I have to produce an output from 3 separate tables(with a couple of fields from each table) into 1 output. I have a class that represents that output. The data is pulled from linq query of EF 6.1.x ObjectContext(Im stuck with using ObjectContext due to the nature of my clients needs....) entities (the 3 classes properly joined in the query) to a list of the new class (List<>). I populate a grid and all is fine. However the user wants to edit the data in the grid and now I need to push those new changes back.
My question is this: Can I map my new class back to the entities field to field? Or am I stuck with iterating through the collection and updating the tables individually? I thought I could map but I haven't run across anything that substantiates this.
Could you not do this using the "Proxy" pattern?
I've done a 2 entity + Wrapper example pseudo example below.
EF would "Save" the SuperWrapper.DeptProxy and the SuperWrapper.EmpProxy.
public partial class DepartmentEFEntity {
public virtual Guid? DepartmentUUID { get; set; }
public virtual string DepartmentName { get; set; }
public virtual ICollection<EmployeeEFEntity> Employees { get; set; }
}
public partial class EmployeeEFEntity
{
public virtual Guid? ParentDepartmentUUID { get; set; }
public virtual Guid? EmployeeUUID { get; set; }
public virtual DepartmentEFEntity ParentDepartment { get; set; }
public virtual string SSN { get; set; }
}
public class SuperWrapper
{
internal DepartmentEFEntity DeptProxy { get; private set; }
internal EmployeeEFEntity EmpProxy { get; private set; }
public SuperWrapper(DepartmentEFEntity dept, EmployeeEFEntity emp)
{
this.DeptProxy = dept;
this.EmpProxy = emp;
}
public string DepartmentName
{
get { return null == this.DeptProxy ? string.Empty : this.DeptProxy.DepartmentName; }
set { if(null!=this.DeptProxy{this.DeptProxy.DepartmentName =value;}}
}
public string EmployeeSSN
{
get { return null == this.EmpProxy ? string.Empty : this.EmpProxy.SSN; }
set { if(null!=this.EmpProxy{this.EmpProxy.SSN =value;}}
}
}
Related
Having an issue with projection and getting child objects to load. The following is simplified code to represent the logic I'm trying to implement, not the actual code.
public class TicketItem
{
public int TicketItemId { get; set; }
public string TicketReason { get; set; }
public Station Station { get; set; }
public TicketOwner TicketOwner { get; set; }
}
public class Station
{
public int StationId { get; set; }
public string Name { get; set; }
}
public class TicketOwner
{
public int TicketOwnerId { get; set; }
public Employee Employee { get; set; }
public Organization Organization { get; set; }
}
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Organization
{
public int OrganizationId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class CommonReasons
{
public int CommonReasonId { get; set; }
public string Reason { get; set; }
}
public TicketItem GetById(int id)
{
var query = from i in _dataContext.TicketItems
.Include("Station")
.Include("TicketOwner.Employee")
.Include("TicketOwner.Organization")
join r in _dataContext.CommonReasons on i.TicketReason equals r.CommonReasonId.ToString() into r1
from r2 in r1.DefaultIfEmpty()
where i.TicketItemId == id
select new TicketItem {
TicketItemId = i.TicketItemId,
TicketReason = r2.Reason == null ? i.Reason : r2.Reason,
Station = i.Station,
TicketOwner = i.TicketOwner
};
return query
.AsNoTracking()
.FirstOrDefault();
}
Most the code is self-explanatory. The part that is indirectly causing the trouble would be the relationship between TicketItem.TicketReason property (a string) and the CommonReasons entity. From the user interface side, the end-user has an input field of "Reason", and they can select from "common" reasons or input an adhoc reason. They original developer chose to have the TicketReason property contain either the key ID from the CommonReasons table (if the user selected from drop-down) or the adhoc reason typed in.
So, to handle this logic in the linq query, the only way I have found is to do a left join between TicketItem.TicketReason and CommonReasons.CommonReasonId, then use projection to modify the TicketReason column returning either the common reason text or adhoc text. If there is a different way to do this that would get me around the trouble I'm having with projection/include, I'm all ears.
For the "reason" logic, this query works, returning the proper text. The trouble is that none of the "grand-child" objects are returning, i.e. TicketItem.TicketOwner.Employee, TicketItem.TicketOwner.Organization. How do I get those objects to return also?
Changing the structure of the tables would be an absolute last resort, just based on the amount of code that would have to change. There are other spots in the code that are using the above logic but don't need the child objects.
Any help would be appreciated. Hope I've explained enough.
Hello everyone so here is my basic EF class structure:
public abstract class StandardEngineeredModel
{
[Key]
public string ModelNumber { get; set; }
public int VoltageInput { get; set; }
}
public class DualRatedInputEngineeredModel : StandardEngineeredModel
{
public int SinglePhaseVoltageInput { get; set; }
public string SinglePhaseHzInput { get; set; }
public decimal SinglePhaseAmpsInput { get; set; }
public bool SeparateInput { get; set; }
}
And my context:
public class LabelPrintingContext : DbContext
{
public virtual DbSet<StandardEngineeredModel> StandardEngineeredModels { get; set; }
}
What I am trying to do is query for a StandardEngineeredModel. Here is an example of a query I tried:
public StandardEngineeredModel GetEngineeredOrder(string modelNumber)
{
using (context = new LabelPrintingContext())
{
return (from s in context.StandardEngineeredModels
where s.ModelNumber == modelNumber
select s).FirstOrDefault();
}
}
But when executing this it says invalid column name SeparateInput which seems to be happening because of the extra column being added to my StandardEngineeredModels table due to the DualRatedInputEngineeredModel inheriting from it. Not sure how to go about this, I don't want to return an Iqueryable, but no matter what I try it tells me the SeparateInput is an invalid column name.
I also tried casting it first and get the same error:
public StandardEngineeredModel GetEngineeredOrder(string modelNumber)
{
using (context = new LabelPrintingContext())
{
return (from s in context.StandardEngineeredModels.OfType<StandardEngineeredModel>()
where s.ModelNumber == modelNumber
select s).FirstOrDefault();
}
}
Do I need to make a DTO or something? Am I just doing this completely wrong?
Thanks in advance for any opinions / help!
I am using Entity Framework 6 in a project and am having trouble creating a query.
Say my classes are defined like:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString)
{
}
public DbSet<EntityXXX> XXXSet { get; set; }
public DbSet<EntityYYY> YYYSet { get; set; }
}
public class EntityXXX
{
public string XXXName { get; set; }
public int id { get; set; }
public int YYYid { get; set; }
}
public class EntityYYY
{
public string YYYName { get; set; }
public int id { get; set; }
}
The YYYid property of EntityXXX is the 'id' of the EntityYYY instance that it relates to.
I want to be able to fill a Grid with rows where the first Column is XXXName and the second column is YYYName (from its related EntityYYY), but I can't see how to do this?
I'm sure it's really simple, but I'm new to EF.
You need to put a virtual navigation property on your EntityXXX
public virtual EntityYYY YYY { get; set; }
Then you can do a projection:
db.XXXSet
.Select(x => new { x.XXXName, YYYName = x.YYY.YYYName })
.ToList();
Which will get you the list you need.
EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?
I have a .Net 4.5 MVC 5 database first project that I'm playing around with. There's a data access layer (Entity Framework 6), a business logic layer and the MVC layer.
If I have an object with relationships in the data layer:
namespace DataAccess
{
public class Course
{
public int CourseID { get; set; }
public string Title { get; set; }
public ICollection<Lecture> Lectures { get; set; }
public ICollection<Tutor> Tutors { get; set; }
}
public class Lecture
{
public int LectureID { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
public ICollection<Student> Students { get; set; }
}
public class Tutor
{
public int TutorID { get; set; }
public string Name { get; set; }
}
public class Student
{
public int StudentID { get; set; }
public string Name { get; set; }
}
}
And in my business logic layer I have a method that gets courses:
namespace BusinessLogic
{
public static IEnumerable<Course> GetCourses()
{
using (var db = new MyEntities())
{
return db.Courses.Include("Lectures").Include("Lectures.Students").Include("Tutors").ToList();
}
}
}
And I get the data using my controller like this:
public class HomeController : Controller
{
public ActionResult Index()
{
var courses = BusinessLogic.GetCourses();
return View(courses);
}
}
Why is it, when I query my data in the Razor view like this:
var numLectures = courses.Lectures.Count;
var numStudents = courses.Lectures.Students.Count;
var tutorName = courses.Tutors.LastOrDefault().Name;
I get the application error System.ObjectDisposedException: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
I know the connection is disposed after the using statement has finished and that .ToList() will let me navigate the courses object, but how do I navigate the objects inside each course (i.e. lectures, students, tutors etc.)?
Your navigation properties need to be declared as virtual:
namespace DataAccess
{
public class Course
{
public int CourseID { get; set; }
public string Title { get; set; }
public virtual ICollection<Lecture> Lectures { get; set; }
public virtual ICollection<Tutor> Tutors { get; set; }
}
public class Lecture
{
public int LectureID { get; set; }
public string Title { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
...
}
When these lazy loadable properties are not marked as virtual, the EF dynamic proxies cannot override them and you will never be able to navigate from one entity to a (set of) another.
Another bit of advice: use the strongly-typed .Include when eager loading:
namespace BusinessLogic
{
public static IEnumerable<Course> GetCourses()
{
using (var db = new MyEntities())
{
return db.Courses
.Include(x => x.Lectures.Select(y => y.Students))
.Include(x => x.Tutors)
.ToList();
}
}
}
I think the problem is because one (or more than one) property that you are calling in your View is (are) not included in your query. Make sure you are including all the navigation properties you need in the view. Try with this query:
using (var db = new MyEntities())
{
return db.Courses.db.Courses.Include(c=>c.Lectures.Select(l=>l.Students)).Include(c=>c.Tutors).ToList()
}
If you need to add another relative property that you use in your View, then add another Include call for that property.
Another thing, when you need to eager load two levels (like Lectures.Students), you don't need to add a Include call for each level, with the call that you do for the second level is enough to include both. Could be this way:
.Include("Lectures.Students") // as you did it
Or:
.Include(c=>c.Lectures.Select(l=>l.Students))