I am working with Entity Framework 6.
I am working with 2 classes:
public partial class StateProvince
{
public StateProvince()
{
Addresses = new HashSet<Address>();
}
public int StateProvinceID { get; set; }
public string StateProvinceCode { get; set; }
public string CountryRegionCode { get; set; }
public bool IsOnlyStateProvinceFlag { get; set; }
public string Name { get; set; }
public int TerritoryID { get; set; }
public Guid rowguid { get; set; }
public DateTime ModifiedDate { get; set; }
public ICollection<Address> Addresses { get; set; }
public CountryRegion CountryRegion { get; set; }
}
public partial class CountryRegion
{
public CountryRegion()
{
StateProvinces = new HashSet<StateProvinceTlb>();
}
public string CountryRegionCode { get; set; }
public string Name { get; set; }
public DateTime ModifiedDate { get; set; }
public virtual ICollection<StateProvince> StateProvinces { get; set;}
}
I would like to be able to run a query that would return a list of StateProvince, but also include the name from CountryRegion. The idea is that on an edit screen, the user will select, or edit the CountryRegionCode, but the Name will display beside it in a none editable field just for reference.
I tried adding property as a none mapped field to StateProvince and referencing the property on CountryRegion, like below:
[NotMapped]
public string CountryName
{
get{ return CountryRegion.Name;}
}
but the problem with this is the CountryRegion has to be loaded in order for that to work, and my goal is to not have to load the entire CountryRegion object.
I've also tried to set it in my query like this:
List<StateProvince> statP = context.StateProvinces.Select(s => new StateProvince() {s, CountryName = context.CountryRegions.Where(x => x.CountryRegionCode == s.CountryRegionCode).Select(x => x.Name).FirstOrDefault() }).ToList();
but this doesn't work because the object returned is comprised of a StateProvince object and a separate CountryName property.
I've even tried setting each of the fields individually like:
List<StateProvince> statP = context.StateProvinces.Select(s => new StateProvince() { Name = s.Name, TerritoryID = s.TerritoryID, rowguid = s.rowguid, ModifiedDate = s.ModifiedDate, Addresses=s.Addresses, CountryRegion=s.CountryRegion, CountryName = context.CountryRegions.Where(x => x.CountryRegionCode == s.CountryRegionCode).Select(x => x.Name).FirstOrDefault() }).ToList();
But this again causes the entire CountryRegion object to load, and if I leave those properties out, an exception is thrown. Also, for larger entities this will be hard to maintain.
The TLDR; version of this: Is there a way in Entity Frameworks to have a class mapped to one table, but have a property on that class that references a property on another table without having to retrieve everything on the child table?
I've searched and searched and can't really find much covering this specific kinda of scenario. I'm fairly new to Entity Framework, and I feel like I'm missing something obvious. Any help would be greatly appreciated!!
SOLUTION
This is what I've come up with to solve the issue.
First, I split the CountryRegion table into two separate classes
public partial class CountryRegionHeader
{
public CountryRegionHeader()
{
StateProvinces = new HashSet<StateProvinceTlb>();
}
public string CountryRegionCode { get; set; }
public string Name { get; set; }
}
public partial class CountryRegionDetail
{
public CountryRegionDetail()
{
StateProvinces = new HashSet<StateProvinceTlb>();
}
public string CountryRegionCode { get; set; }
public DateTime ModifiedDate { get; set; }
public virtual ICollection<StateProvince> StateProvinces { get; set;}
public virtual CountryRegion CountryRegion {get;set;}
}
I then add the properties for the new classes to my StateProvince class
[ForeignKey("CountryRegionCode)]
public CountryRegionHeader CountryRegionHeader {get;set;}
[ForeignKey("CountryRegionCode)]
public CountryRegionDetail CountryRegionDetail {get;set;}
I then add the DBSets to my model context for CountryRegionHeader and CountryRegionDetail and tie them together using the fluent API in the OnModelCreating method
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CountryRegionHeader>()
.HasRequired(e => e.CountryRegionDetail)
.WithRequiredPrincipal();
}
In addition to this, I've created another class just called CountryRegion which has all of the properties from both the Header and Detail as well as the Header and Detail object themselves. The properties actually point back to the header and detail. This really isn't necessary, but it makes the code cleaner and easier to use. Also, when sending the data down to a web client I can just serialize the CountryRegion object and exclude the Header and Detail object. So basically my CountryRegion class looks like this:
public Class CountryRegion
{
public CountryRegionHeader Header;
public CountryRegionDetail Detail;
public CountryRegionCode
{
//need some special logic here for the key since both Header or
//Detail share the primary key and both may not be loaded
get
{
if (Header != null)
return Header.CountryRegionCode;
else
return Detail.CountryRegionCode;
}
set
{
if (Header != null) Header.CountryRegionCode = value;
if (Detail != null) Detail.CountryRegionCode = value;
}
}
public string Name
{
get
{
return Header.Name;
}
set
{
Header.Name = value;
}
}
public DateTime ModifiedDate
{
get
{
return Detail.ModifiedDate ;
}
set
{
Detail.ModifiedDate = value;
}
}
public virtual ICollection<StateProvince> StateProvinces
{
get
{
return Detail.StateProvinces ;
}
set
{
Detail.StateProvinces = value;
}
}
}
So, Now When I query I can do something like:
List<StateProvince> query = db.StateProvince.Include(o=>o.CountryRegionHeader).ToList();
And I only retrieve the data I need without retrieving the entire CountryRegion record
Also, If I'm working with just the CountryRegion, I can query like this:
List<CountryRegion> query = (from a in db.CountryRegionHeader join b in db.CountryRegionDetail on a.CountryRegionCode equals b.CountryRegionCode select new Employee(){Header = a, Detail = b}).ToList();
SOLUTION
This is what I've come up with to solve the issue.
First, I split the CountryRegion table into two separate classes
public partial class CountryRegionHeader
{
public CountryRegionHeader()
{
StateProvinces = new HashSet<StateProvinceTlb>();
}
public string CountryRegionCode { get; set; }
public string Name { get; set; }
}
public partial class CountryRegionDetail
{
public CountryRegionDetail()
{
StateProvinces = new HashSet<StateProvinceTlb>();
}
public string CountryRegionCode { get; set; }
public DateTime ModifiedDate { get; set; }
public virtual ICollection<StateProvince> StateProvinces { get; set;}
pubic virtual CountryRegion CountryRegion {get;set;}
}
I then add the properties for the new classes to my StateProvince class
[ForeignKey("CountryRegionCode)]
public CountryRegionHeader CountryRegionHeader {get;set;}
[ForeignKey("CountryRegionCode)]
public CountryRegionDetail CountryRegionDetail {get;set;}
I then add the DBSets to my model context for CountryRegionHeader and CountryRegionDetail and tie them together using the fluent API in the OnModelCreating method
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CountryRegionHeader>()
.HasRequired(e => e.CountryRegionDetail)
.WithRequiredPrincipal();
}
In addition to this, I've created another class just called CountryRegion which has all of the properties from both the Header and Detail as well as the Header and Detail object themselves. The properties actually point back to the header and detail. This really isn't necessary, but it makes the code cleaner and easier to use. Also, when sending the data down to a web client I can just serialize the CountryRegion object and exclude the Header and Detail object. So basically my CountryRegion class looks like this:
public Class CountryRegion
{
public CountryRegionHeader Header;
public CountryRegionDetail Detail;
public CountryRegionCode
{
//need some special logic here for the key since both Header or
//Detail share the primary key and both may not be loaded
get
{
if (Header != null)
return Header.CountryRegionCode;
else
return Detail.CountryRegionCode;
}
set
{
if (Header != null) Header.CountryRegionCode = value;
if (Detail != null) Detail.CountryRegionCode = value;
}
}
public string Name
{
get
{
return Header.Name;
}
set
{
Header.Name = value;
}
}
public DateTime ModifiedDate
{
get
{
return Detail.ModifiedDate ;
}
set
{
Detail.ModifiedDate = value;
}
}
public virtual ICollection<StateProvince> StateProvinces
{
get
{
return Detail.StateProvinces ;
}
set
{
Detail.StateProvinces = value;
}
}
}
So, Now When I query I can do something like:
List<StateProvince> query = db.StateProvince.Include(o=>o.CountryRegionHeader).ToList();
And I only retrieve the data I need without retrieving the entire CountryRegion record
Also, If I'm working with just the CountryRegion, I can query like this:
List<CountryRegion> query = (from a in db.CountryRegionHeader join b in db.CountryRegionDetail on a.CountryRegionCode equals b.CountryRegionCode select new Employee(){Header = a, Detail = b}).ToList();
One way to achieve this is to add a computed column on your database that gives you the CountryRegion name on your StateProvince.
With this all you have to do is to request the StateProvince table and your database server will give you the relevant associated CountryRegion name.
Edit
After your comment I had another idea.
Create a class for your usage :
public class StateProvinceEdition : StateProvince
{
public string CountryRegionName { get; set; }
public StateProvinceEdition(StateProvince stateProvince, string countryRegionName)
{
this.StateProvinceID = stateProvince.StateProvinceID;
// And so on ...
// Adding country region name
this.CountryRegionName = countryRegionName;
}
}
Then you need to project your query result into your object using Linq :
List<StateProvinceEdition> statP = context.StateProvinces.Select(s => new StateProvince(s, s.CountryRegion.Name)).ToList();
I do not have a database to test it upon so if EF throws an exception regarding the constructor, try to switch the Select and ToList statements to first load objects from the database and then project the result in the custom class.
Second edit
Similar to the first idea, you can create a view in your database for your purpose and then use Entity Framework to query this view. With this solution you do not need to change your database other than adding a view.
Related
I confused with error that Action returns me. I have a code in my manager:
public class AddressesManager
{
private SiteDBEntities entityContext;
public Addresses GetAddress(short id)
{
entityContext = new SiteDBEntities();
var addressList = entityContext.Addresses.Where(a => a.Id == id).FirstOrDefault();
entityContext.Dispose();
return addressList;
}
}
And action that call this function:
[HttpPost]
public ActionResult LoadAddress(short id)
{
AddressesManager mngr = new AddressesManager();
Addresses address = mngr.GetAddress(id);
return new JsonResult() { Data = address };
}
And jquery code wich call thit action:
$.post("/Cart/LoadAddress", { id: id })
.success(function (data) {
console.log(data);
})
.fail(function (e) { console.log(e) });
Action is running, but i always getting 500 error whit this code:
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
As i understood the problem is with entityContext , but why this happens? I already executed data from DB and I don't need connection anymore...
EDIT:
Thit is my Address model. It autogenerated by EF:
public partial class Addresses
{
public int Id { get; set; }
public string Title { get; set; }
public string State { get; set; }
public string Country { get; set; }
public string Warehouse { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public string Phone { get; set; }
public short DeliveryType { get; set; }
public System.Guid UserId { get; set; }
public virtual DeliveryType DeliveryType1 { get; set; }
public virtual Users Users { get; set; }
}
The error happens because your Address class has 2 virtual properties: DeliveryType1 and Users.
When you convert your address to JSON, it will try to access those virtual properties. However, at that time, your context are disposed already.
To avoid this problem, you shouldn't return the EF auto-generated class directly. Instead, create a DTO object (Data Transfer Object) containing only some fields you need, map it with the EF object, and return it. For example:
public class AddressesDTO
{
public int Id { get; set; }
public string Title { get; set; }
public string State { get; set; }
public string Country { get; set; }
public string Warehouse { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public string Phone { get; set; }
public short DeliveryType { get; set; }
public System.Guid UserId { get; set; }
}
Then, map it:
public Addresses GetAddress(short id)
{
entityContext = new SiteDBEntities();
var addressList = entityContext.Addresses.Where(a => a.Id == id).FirstOrDefault();
// Create DTO object
AddressesDTO address = new AddressesDTO();
// Map it
address.Id = addressList.Id;
address.Title = addressList.Title
// Go on, it's quite long...
entityContext.Dispose();
return address;
}
However, as you can see, the mapping process is very boring. A better way is using Automapper.
If you want to use your entity data model then I suggest you to take a look to my answer in this post.
Now, what I would do is create a DTO to get only the data you need to pass to your View, like was proposed by #AnhTriet. To avoid map by yourself each property every time you need to project your query to your DTO, I suggest to use Automapper. Let me show you how would be your solution if you decide to use it:
public class AddressesManager
{
public Addresses GetAddress(short id)
{
using(var entityContext = new SiteDBEntities())
{
var address = entityContext.Addresses
.Where(a => a.Id == id)
.ProjectTo<AddressDTO>()
.FirstOrDefault();
return address;
}
}
}
About ProjectTo extension method.
Do you have a navigation (child) property in your Address class? I get the feeling that you're trying to access this child property after the DbContext has been disposed of, as child properties are lazy-loaded by default.
Can you post the code of your Address model?
I think you should add attribute [JsonIgnore] to virtual properties of Addresses Class.
class Addresses
{
public int ID { get; set; }
[JsonIgnore]
public virtual Something Something { get; set; }
}
Addresses should be lazy-loaded in your case. So you are trying to access disposed data and Entity Framework tries to use disposed context and that gives an exception.
Maybe you need to attach your addressList to current context in order to enable lazy-loading
var addressList = entityContext.Addresses.Where(a => a.Id == id).FirstOrDefault();
entityContext.Addresses.Attach(addressList);
When you are returning a json record in MVC you need to add AllowGet like so in your controller:
return Json(new { data= address }, JsonRequestBehavior.AllowGet);
With this in place you should be fine.
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))
The project I am working on use NHibernate, AutoMapper and Fluent. All I need to do is read the XML file and enter the data into database. But there is a problem I am facing. when I try to map the Source and Destination I get the error which I have mentioned in the title.
Below is my code:
public partial class Language
{
public string languageIdField;
public string languageNameField;
}
public partial class Person
{
public int personIdField;
public string firstNameField;
public string lastNameField;
public int stateField;
public int enableEmailField;
public int attestPersonLockedField;
public string emailAddressField;
public string languageId;
}
I am creating above classes with xsd2code tool.But I have simplified it here.
Model classes are:
public class Person
{
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string EmailAddress {get; set;}
.....
public int state {get; set;}
public virtual User User { get; set; }
public virtual Language language { get; set; }
}
public class Language
{
public virtual string LanguageId { get; set; }
public virtual string LanguageName { get; set; }
public virtual IList<Person> Persons { get; set; }
}
And this is how I map them with AutoMapper:
AutoMapper.Mapper.CreateMap<Language, Models.Language>();
AutoMapper.Mapper.CreateMap<Person, Models.Person>();
And that's how I am reading and trying to save the data:
object id = null;
foreach (var item in templateData.Languages)
{
id = save<Dicom.Expense.Models.Language>(item); // this will return the language id
}
Person person = new Person();
person.Emailaddress = templatedata.Person.EmailAddress;
....
person.languageId = id.ToString();
save<Dicom.Expense.Models.Person>(person);
private void save<TModel>(object templateObject)
{
var dbModel = AutoMapper.Mapper.Map<TModel>(templateObject);
repository.Save<object>(dbModel);
}
When I try to save the Person information I get the error:
could not execute batch command.[SQL: SQL not available]
Cannot insert the value NULL into column 'fkLanguageID
And this is because the Person table have the language ID as a foreign key in it. Now I do not know what changes I need to do so that Person source and destination map properly and save the data into database.
EDIT:
I have realized that I need to change the Person.languageId value into PersonModel.Language object so that NHibernate can read it and map it. Is it possible to use Customer Resolver or Type Converter to achieve this?
This is what I am trying to do:
AutoMapper.Mapper.CreateMap<Person, Models.Person>().ForMember(dest => dest.Language, opt => opt.ResolveUsing<CustomResolver>());
public class CustomResolver : ValueResolver<Person, Models.Language>
{
public Dicom.Expense.Models.Language ResolveCore(Person source)
{
?????
}
}
I solved the problem by creating a Resolver which AutoMapper provide for complex mapping. While creating a mapping I told the mapper to resolve the Destination Language with Source Language ID by resolving it into an object of type Language.
Below is my code;
AutoMapper.Mapper.CreateMap<Person, Models.Person>()
.ForMember(x => x.Language, opt => opt.ResolveUsing(new LanguageCodeResolver(loadRepository)).FromMember(x => x.LanguageId));
public class LanguageCodeResolver : ValueResolver<string, Dicom.Expense.Models.Language>
{
private IDatabaseLoadRepository loadRepository;
public LanguageCodeResolver(IDatabaseLoadRepository loadRepository)
{
this.loadRepository = loadRepository;
}
protected override Models.Language ResolveCore(string languageCode)
{
return loadRepository.FindOne<Models.Language>(x => x.LanguageId == languageCode);
}
}
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;}}
}
}
I am using Automapper to map my Model objects to DTO. In DTO the primary key should be replaced with the corresponding object. For this purpose I used the code below:
// Model class
public class SubDepartment
{
public long Id { get; set; }
public string Name { get; set; }
public long? DepartmentId { get; set; }
public DateTime LastUpdated { get; set; }
}
// DTO class
public class SubDepartmentDTO
{
public long Id { get; set; }
public string Name { get; set; }
public Department Department { get; set; }
public long EventCount { get; set; }
}
// Mapping code
Mapper.CreateMap<Models.Event.SubDepartment, DTO.SubDepartment>().ForMember(dto => dto.Department,
map => map.MapFrom(sd => Mapper.Map<Department, DTO.Department>(_departmentRepository.GetById(sd.DepartmentId.Value))));
But when I map from SubDepartment to SubDepartmentDTO in my controller, the 'Department' object is always null. I tried replacing the _departmentRepository.GetById(sd.DepartmentId.Value) code with a hardcoded Department object and it is working good. I also verified there is a corresponding Department exist in the database for the primary key. Can anyone point out what I am doing wrong ?
Try it with this code
Mapper.CreateMap<Models.Event.SubDepartment, DTO.SubDepartment>().ForMember(dto => dto.Department,map => map.MapFrom(sd => _departmentRepository.GetById(sd.DepartmentId.Value)));
if it doesn't work you could try Custom value resolver
Models.Event.SubDepartment, DTO.SubDepartment>().ForMember(dto => dto.Department, map => map.ResolveUsing<DepartmentResolver>());
public class DepartmentResolver: ValueResolver<Models.Event.SubDepartment,DTO.SubDepartment>
{
Reporsitory _departmentRepository;
protected override DTO.SubDepartment ResolveCore(Models.Event.SubDepartment source)
{
return _departmentRepository.GetById(source.DepartmentId.Value);
}
}