I am using Visual Studio 2010, C# 4.0 and Entity Framework 5.0. I have been using database first development for many years but am trying to move to code first and am running into problems. Reading and searching does not seem to address the problems
I have simplified my problem as follows - I have two classes - Assessors and Documents.
public class Assessor
{
public int AssessorID { get; set; }
public virtual List<Document> Documents { get; set; }
}
public class Document
{
public int DocumentID { get; set; }
public string DocumentLocation { get; set; }
public string DocumentName { get; set; }
public virtual List<Assessor> Assessors { get; set; }
}
with the context
public class DocumentAssignment : DbContext
{
public DbSet<Assessor> Assessors { get; set; }
public DbSet<Document> Documents { get; set; }
}
An assessor can have many documents and a document can have many assessors (a classic many-to-many relationship).
I am using convention to create the relationship but have also used the fluent API. I have seeded the document table.
My two questions:
ONE - I want to assign documents to assessors - what is the best way to save this to the database?
TWO I have the following method to retrieve documents assigned to an assessor:
public static IEnumerable<MaternalDocument> GetAssignedDocumentList(int UserID, string ConnectionString)
{
using (DocumentAssignment dbContext = new DocumentAssignment(ConnectionString))
{
return returnValue = dbContext.MaternalAssessments
.Where(m => m.AssessorID == UserID)
.Include(m => m.MaternalDocuments)
.Select(m => m.MaternalDocuments)
.ToList();
}
}
but I cannot get this to compile because of mapping issues. What am I doing wrong?
You have to tell the DbContext about how the many-to-many relationship is set up, by overriding OnModelCreating in DocumentAssignment. Replace AssessorDocuments in this code with your relation table name.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Assessor>()
.HasMany(a => a.Documents)
.WithMany(d => d.Assessors)
.Map(m =>
{
m.MapLeftKey("AssessorID");
m.MapRightKey("DocumentID");
m.ToTable("AssessorDocuments");
});
base.OnModelCreating(modelBuilder);
}
To assign a Document to an Assessor (assuming a Document exists with DocumentID of 1 and an Assessor exists with an AssessorID of 1):
using (var context = new DocumentAssignment())
{
var assessor = context.Assessors.Find(1);
var document = context.Documents.Find(1);
assessor.Documents.Add(document);
context.SaveChanges();
}
Your GetAssignedDocumentList method would look something like this:
public static IEnumerable<Document> GetAssignedDocumentList(int UserID)
{
using (var context = new DocumentAssignment())
{
return context.Documents.Where(d => d.Assessors.Any(a => a.AssessorID == UserID));
}
}
Related
I've just started to work with Entity Framework Core 6.
I am working with a sample database where I have a many to many relationship.
I created my database on SQL server. I created three tables: Service, Document, ServiceDocs (used as a Junction Table).
Then I did :
scaffolf-dbcontext
both classes have been generated except the junction table ServiceDocs.
My question is: How can I add elements to the junction table and get data from it without the class of the junction table?
Thank you for your help.
Class document:
public partial class Document
{
public Document()
{
Services = new HashSet<Service>();
}
public Guid DocumentId { get; set; }
public string? DocTitre { get; set; }
public virtual ICollection<Service> Services { get; set; }
}
public partial class Service
{
public Service()
{
Docs = new HashSet<Document>();
}
public Guid ServiceId { get; set; }
public string? Libelle { get; set; }
public virtual ICollection<Document> Docs { get; set; }
}
Here some screenshots :
Database diagram
Document
Service
var result = await _dbContext.BillingGroupFakes
.Where(b => b.Customers.FirstOrDefault().ExternalCustomerId.Equals($"{id}"))
.Include(b => b.Customers)
.Select(m => new
{
m.Customers.FirstOrDefault().CustomerId,
CustomerName = $"{m.Customers.FirstOrDefault().CustomerLastName}, {m.Customers.FirstOrDefault().CustomerName}",
m.BillingGroupId,
m.BillingGroupCode,
m.BillingGroupDescription
})
.AsNoTracking()
.ToListAsync();
I found the answer how to get the data:
var services = await _context.Services
.Where(s => s.ServiceId == Id)
.Include(s =>s.Docs)
.ToListAsync();
return services;
Thank you.
I want to have a single table where I can retrieve data using either a summary entity or a detailed entity. All data will be added using the detailed entity but often I just need a summary of each record for building a list and don't need some of the larger fields to be loaded for this for speed. I've tried the code below but it adds in a discriminator, which I don't want. How can I do this properly?
Ideally it would prevent records being added with the summary but I'm not so concerned about that.
public class Summary
{
public Guid Id { get; set; }
[StringLength(254)]
public string Name { get; set; } = string.Empty;
}
public class Detailed : Summary
{
public string BigField { get; set; } = string.Empty;
}
public class MyDbContext : DbContext
{
public DbSet<Summary> Summaries { get; set; } = null!;
public DbSet<Detailed> Details { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Summary>(
s =>
{
s.ToTable("Details");
});
modelBuilder.Entity<Detailed>(
s =>
{
s.ToTable("Details");
});
}
}
I think what you're looking for is called Table per hirarchy:
https://learn.microsoft.com/en-us/ef/core/modeling/inheritance#table-per-type-configuration
another solution would be to add the Detailed to the db and only select the needed fields in your queries
dbContext
.Details
.Select(d => new Summary { Id = d.Id, Name = d.Name })
.ToListAsync();
this gets translated to the sql query:
SELECT Id, Name
From Detailed
and therefore you don't query all the data.
I am very new to Entity Framework Core (Entity Framework in General), and I have watched several tutorial videos and done a Pluralsight course.
I am using ASP.NET Web API, and I want to add a new entity, that has a One-to-Many relationship. The models are as follows:
"Parent" Class:
public partial class VerificationVoltage
{
public int Id { get; set; }
public DateTime Date { get; set; }
public string Type { get; set; }
public int VerificationVoltageSerialId { get; set; }
public VerificationVoltageSerial Serial { get; set; }
}
"Child" Class:
public class VerificationVoltageSerial
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[MaxLength(20)]
public string Serial { get; set; }
public List<VerificationVoltage> VerificationVoltage { get; set; }
}
I would like the VerificationVoltageSerial.Serial to be unique, and that the database will first check whether or not the serial exists, before adding the newly added serial (The serials themselves are unique).
Is there a way to do the following: If a row with the same serial exist, as what needs to be added, entity framework then automatically selects the Serial and SerialId and populates the Entity that is being added, with the serial selected from the database.
This is what I am doing currently, I feel that this is a very manual check, an maybe there is something more automatic: (i.e I want it to wrok, even if I remove the IF Statement and the exisitngEntry query in the controller)
[HttpPost]
public async Task<VerificationVoltage> AddVerificationVoltage(VerificationVoltage verificationVoltage)
{
var exisitngEntry = await repository.GetBySerialNoTracking(verificationVoltage.Serial.Serial)
.ConfigureAwait(false);
if (exisitngEntry != null)
{
var existingSerial = exisitngEntry.Serial;
verificationVoltage.Serial.Id = existingSerial.Id;
verificationVoltage.Serial = new VerificationVoltageSerial()
{
Id = existingSerial.Id,
Serial = existingSerial.Serial,
};
}
var addedEntry = repository.Add(verificationVoltage);
await repository.SaveChanges().ConfigureAwait(false);
return addedEntry;
}
public async Task<VerificationVoltage> GetBySerialNoTracking(string serialNumber)
{
return await DbSet.Include(a => a.Serial)
.Where(a => a.Serial.Serial.Equals(serialNumber))
.OrderBy(a => a.VerificationVoltageSerialId)
.AsNoTracking()
.FirstOrDefaultAsync()
.ConfigureAwait(false);
}
My protected override void OnModelCreating(ModelBuilder modelBuilder) method, only repeats the attributes:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ValidationLabAmbientMeasurements>()
.HasOne(a => a.AmbientMeasurementsIdentifier)
.WithMany();
modelBuilder.Entity<VerificationVoltage>()
.Property(a => a.Id)
.ValueGeneratedOnAdd();
modelBuilder.Entity<VerificationVoltage>()
.HasOne(a => a.Serial)
.WithMany(nameof(VerificationVoltage));
modelBuilder.Entity<VerificationVoltage>()
.HasMany(a => a.VerificationVoltageMeasurements)
.WithOne(nameof(VerificationVoltage));
}
I have tried searching for answers, but my search queries do not get results on this specific issue.
I cannot get a table to update correctly that should be linking two of my entities. To explain in more detail...I have two entities, Class and Teacher, with a relationship in the form of:
Teacher can be assigned to many classes
Class can only have one teacher.
Below are these two entities.
public class Teacher
{
[Required, Key]
public Guid Id { get; private set; }
[StringLength(50)]
public string Name { get; set; }
public string Email { get; set; }
public List<Class> Classes = new List<Class>();
public Teacher()
{
Id = new Guid();
}
public Teacher(Guid id)
{
Id = id;
}
public void AssignClass(Class newClass)
{
Classes.Add(newClass);
}
}
public class Class
{
[Required, Key]
public Guid Id { get; private set; }
[Required, StringLength(20)]
public string Name { get; set; }
[Required, Range(5, 30)]
public int Capacity { get; set; }
public Teacher Teacher { get; set; }
public IEnumerable<StudentClass> StudentClasses { get; set; }
public Class()
{
Id = new Guid();
}
public Class(Guid id)
{
Id = id;
}
}
When I generate my migrations I get a foreign key of TeacherId in the Classes table as expected. Here is the SQL:
CREATE TABLE [dbo].[Classes] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[Name] NVARCHAR (20) NOT NULL,
[Capacity] INT NOT NULL,
[TeacherId] UNIQUEIDENTIFIER NULL,
CONSTRAINT [PK_Classes] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Classes_Teachers_TeacherId] FOREIGN KEY ([TeacherId]) REFERENCES [dbo].[Teachers] ([Id])
);
GO
CREATE NONCLUSTERED INDEX [IX_Classes_TeacherId]
ON [dbo].[Classes]([TeacherId] ASC);
My class derived of DBContext looks like:
public class SchoolDatabaseContext : DbContext
{
public DbSet<Student> Students { get; private set; }
public DbSet<Class> Classes { get; private set; }
public DbSet<Teacher> Teachers { get; private set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
public SchoolDatabaseContext(DbContextOptions<SchoolDatabaseContext> options) : base(options)
{
}
}
No configuration for those entities yet. I use DI to serve the DbContext to the controller and that all seems fine.
I have aimed for a DDD type structure, but to make this issue easier to debug I have stripped everything all the way back to the controller so it is basically... controller => DbContext.
Here is my code in the controller:
[HttpPost]
[Route("assign-teacher-to-class")]
public async Task<IActionResult> AssignClass([FromBody] AssignTeacherToClass assignTeacherToClass)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var teacher = await schoolDatabaseContext.Teachers.FindAsync(assignTeacherToClass.TeacherId);
var classToAssign = await schoolDatabaseContext.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
schoolDatabaseContext.Entry(teacher).State = EntityState.Modified;
await schoolDatabaseContext.SaveChangesAsync();
return Ok(teacher);
}
When I debug through the ids are fine from the post body, they are assigned correctly to the DTO AssignClass and the calls to the DbContext to find the data for each type (teacher and class) are fine. I then call a method in my teacher type to add the class to the List Classes property (see teachers entity code at beginning for reference), I then Save the changes with the DbContext method and Problem Defined Here: at no stage does the TeacherId in the database update whilst debugging/completing. I have tried all I can think of like instantiating collections in different ways, changing collection types, looking for config that might help map these entities in this way, stripping out all extra layers, changing accessibility of properties and classes and few more.
Any help would really be appreciated as I am getting a bit defeated on this one and I feel like this relationship should be fairly straight forward. I actually was able to get my many to many working with a bridge class so I was surprised to get stuck on this one :(
Thanks
try this:
var teacher = await schoolDatabaseContext.Teachers.Include(x => x.Classes).SingleOrDefaultAsync(x => x.Id == assignTeacherToClass.TeacherId);
I don't think teacher.Classes gets tracked by DbContext otherwise.
There are multiple ways to accomplish this with EF Core. It is easiest to find if you call it what the docs call it "Related Data".
Here is the parent doc: Related Data
Specifically as #Y Stroli has illustrated the Eager Loading method.
The below example is shown on the eager loading reference to load multiple levels of related data:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.ToList();
}
As of EF Core 5.0 you can also do filtered includes:
using (var context = new BloggingContext())
{
var filteredBlogs = context.Blogs
.Include(blog => blog.Posts
.Where(post => post.BlogId == 1)
.OrderByDescending(post => post.Title)
.Take(5))
.ToList();
}
As the suggestion from lvan, you should change public List<Class> Classes = new List<Class>(); to public List<Class> Classes { get; set; } = new List<Class>();.
For your current code, it seems you want to add Class and return the teacher, if so, you need to include the exsiting classes to teacher like below, otherwise, it will only return the new adding class.
public async Task<IActionResult> AssignClass()
{
var assignTeacherToClass = new AssignTeacherToClass {
TeacherId = new Guid("52abe5e0-bcd4-4827-893a-26b24ca7b1c4"),
ClassId =new Guid("50354c76-c9e8-4fc3-a7c9-7644d47a6854")
};
var teacher = await _context.Teachers.Include(t => t.Classes).FirstOrDefaultAsync(t => t.Id == assignTeacherToClass.TeacherId);
var classToAssign = await _context.Classes.FindAsync(assignTeacherToClass.ClassId);
teacher.AssignClass(classToAssign);
_context.Entry(teacher).State = EntityState.Modified;
await _context.SaveChangesAsync();
return Ok(teacher);
}
One more note, you need to configure SerializerSettings.ReferenceLoopHandling like
services.AddMvc()
.AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You need to define the connection between Teacher and Class.
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Class>()
.HasOne<Teacher>(p => p.Teacher)
.WithMany(q => q.Classes)
.HasForeignKey(r => r.TeacherId);
}
Also add TeacherId prop to Class.
I have 2 classes that reference each other. It's a weird situation that our CRM needs.
I have an Organization and EmAddress tables and classes. The Organization inherits from Subscriber, which also has a table. I think this could be my problem, or the fact that I can't set Inverse on these because there is no "HasMany"...
The order of insert/update is ..
INSERT Email
INSERT Org
UPDATE Email to set Email.Subscriber
Email.Subscriber needs to be "NOT NULL", so this doesn't work. How can I change the order, I can't use Inverse because there is no list. Just 2 references.
public class Organization : Subscriber
{
public override string Class { get { return "Organization"; } }
EmAddress PrimaryEmailAddress {get;set;}
}
public class OrganizationMap : SubclassMap<Organization>
{
public OrganizationMap()
{
Table("Organization");
KeyColumn("Organization");
References(x => x.PrimaryEmail,"PrimaryEmailAddress").Cascade.SaveUpdate();
}
}
public EmAddressMap()
{
Id(x => x.Id, "EmAddress");
Map(x => x.EmailAddress, "eMailAddress");
References<Subscriber>(x => x.Subscriber,"Subscriber").LazyLoad().Fetch.Select().Not.Nullable();
/*not.nullable() throw s error. NHibernate INSERTS email, INSERTS org, UPDATES email. */
}
public class EmAddress
{
public virtual Guid Id { get; set; }
public virtual string EmailAddress { get; set; }
public virtual Subscriber Subscriber { get; set; }
}
//Implementation
var session = NHIbernateHelper.GetSession();
using(var tx = session.BeginTransaction())
{
var org = new Organization();
org.PrimaryEmail = new EmAddress(){Subscriber = org};
session.Save(org);
tx.commit();
}
This post might help:
http://ayende.com/blog/3960/nhibernate-mapping-one-to-one
Have only one side use many-to-one (Fluent: "References") and the other side uses one-to-one (Fluent: "HasOne").