It's been quite a while since I last used EF. I've never had any problems using it before. Now I'm attempting to insert an object that has a one-many relationship with another object. But in the API call, the collection array of the child object is shown to be empty however the parent object can be seen in the api call of the child object.
I have my models as below:
Conversation Table
public class Conversation
{
public Conversation()
{
this.ChatMessages = new List<ChatMessage>();
this.DeletedConversations = new List<ConversationDeleted>();
}
public int ConversationID { get; set; }
public string toUser { get; set; }
public string FromUser { get; set; }
[InverseProperty("Conversation")]
public ICollection<ChatMessage> ChatMessages { get; set; }
public ICollection<ConversationDeleted> DeletedConversations { get; set; }
public DateTime CreatedAt { get; set; }
public int UserID { get; set; }
}
ChatMessage Table
public class ChatMessage
{
public int ChatMessageID { get; set; }
public string fromUser { get; set; }
public string toUser { get; set; }
public string Message { get; set; }
public bool DeliveryStatus { get; set; }
public DateTime CreatedAt { get; set; }
public Guid UniqueID { get; set; }
public int ConversationID { get; set; }
[ForeignKey("ConversationID")]
public virtual Conversation Conversation { get; set; }
public ICollection<MessageDeleted> MessagesDeleted { get; set; }
public int UserId { get; set; }
}
My Fluent API looks like this:
modelBuilder.Entity<ChatMessage>()
.HasRequired(x => x.Conversation)
.WithMany(x => x.ChatMessages)
.HasForeignKey(x => x.ConversationID);
I'm trying to create a conversation entity and add a chat object to it's collection. I do it like so:
public IHttpActionResult CreateConversation()
{
ChatMessage msg = new ChatMessage { CreatedAt = DateTime.UtcNow, DeliveryStatus = true, fromUser = "annettehiggs", toUser = "terrydriscoll", Message = "Hum tum", UniqueID = Guid.NewGuid(), UserId = 43 };
Conversation conv = new Conversation();
conv.ChatMessages.Add(msg);
conv.CreatedAt = DateTime.UtcNow;
conv.FromUser = "annettehiggs";
conv.toUser = "terrydriscoll";
DataModel db = new DataModel();
db.Conversations.Add(conv);
db.SaveChanges();
return Ok(conv);
}
and this is how I retrieve the conversation object:
public IQueryable<Conversation> GetConversations()
{
return db.Conversations;
}
As a result, ChatMessage API call shows the conversation it's associated to but the Conversation object doesn't show the chat in it's collection. What am I doing wrong here?
The add code is working properly (otherwice you'll not be able to see the new chat message). The problem is with your data retrieval code.
Since your ChatMessage.Conversation property is marked as virtual, most probably it gets lazy loaded, that's why you see it populated.
At the same time, your Conversation.ChatMessages is not virtual, hence you need to explicitly eager load it using the Inlclude method, or depending on your requirements, mark it virtual to get the lazy load behavior like the inverse navigation property.
Related
When trying to create a new database entry of type TestForm2 I include the related object Unit Type's ID as a foreign key, except when I perform context.SaveChanges() after adding the new model I get the following SQL exception:
SqlException: Violation of PRIMARY KEY constraint 'PK_dbo.UnitTypes'. Cannot insert duplicate key in object 'dbo.UnitTypes'. The duplicate key value is (2d911331-6083-4bba-a3ad-e50341a7b128). The statement has been terminated.
What this means to me is that it thinks that the foreign entry I'm trying to relate to the new model is instead a new object that it's attempting to insert into the UnitTypes table and failing because it sees an existing entry with the same primary key.
For context (pun not intended), this is my data context, the database model, and the erroring "Create" function.
public class DataContext : IdentityDbContext<ApplicationUser>
{
public DataContext() : base("DefaultConnection")
{
}
public static DataContext Create()
{
return new DataContext();
}
public DbSet<SafetyIncident> SafetyIncidents { get; set; }
public DbSet<ProductionLine> ProductionLines { get; set; }
public DbSet<ProductionOrder> ProductionOrders { get; set; }
public DbSet<SerialOrder> SerialOrder { get; set; }
public DbSet<QualityError> QualityErrors { get; set; }
public DbSet<PSA> PSAs { get; set; }
public DbSet<TestStation> TestStations { get; set; }
public DbSet<ProductionGoal> ProductionGoals { get; set; }
public DbSet<DailyWorkStationCheck> DailyWorkStationChecks { get; set; }
public DbSet<TestForm> TestForms { get; set; }
public DbSet<User> AppUsers { get; set; }
public DbSet<Options> Options { get; set; }
public DbSet<DriveList> DriveSerials { get; set; }
public DbSet<MRPController> MRPControllers { get; set; }
public DbSet<TestOption> TestOptions { get; set; }
public DbSet<UnitType> UnitTypes { get; set; }
public DbSet<UnitTypeMap> UnitTypeMaps { get; set; }
public DbSet<TestForm2> TestForm2s { get; set; }
public DbSet<TestFormSection> TestFormSections { get; set; }
public DbSet<TestFormSectionStep> TestFormSectionSteps { get; set; }
}
public class TestForm2 : BaseEntity
{
public string SerialNumber { get; set; }
public string MaterialNumber { get; set; }
public string UnitTypeId { get; set; }
public UnitType UnitType { get; set; }
public bool UsesStandardOptions { get; set; }
public bool OptionsVerified { get; set; } // This will only be used when UsesStandardOptions is true, otherwise its value doesn't matter
public ICollection<TestOption> AllOptions { get; set; } // List of all options (at time of form creation)
public ICollection<TestOption> Options { get; set; } // The options on a unit
public ICollection<TestFormSection> Sections { get; set; }
}
public FormViewModel Create(FormViewModel vm)
{
using (var context = new DataContext())
{
List<string> optionListStrings = GetOptionListForModelNumber(vm.MaterialNumber); // returns list of option codes
List<TestOption> matchingOptions = context.TestOptions
.Where(optionInDb =>
optionListStrings.Any(trimOption => trimOption == optionInDb.OptionCode)).ToList();
var unitType = context.UnitTypes.FirstOrDefault(x => x.Name == vm.UnitType);
string unitTypeId = unitType.Id;
TestForm2 newForm = new TestForm2
{
// ID & CreatedAt instantiated by Base Entity constructor
SerialNumber = vm.SerialNumber,
MaterialNumber = vm.MaterialNumber,
UnitTypeId = unitType.Id,
UsesStandardOptions = vm.UsesStandardOptions,
OptionsVerified = vm.OptionsVerified,
//AllOptions = context.TestOptions.ToList(),
//Options = matchingOptions,
Sections = vm.Sections,
};
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
context.TestForm2s.Add(newForm);
context.SaveChanges(); // THIS IS WHERE THE SQL EXCEPTION IS HAPPENING
return vm;
}
return null;
}
Lastly, I'm not sure if it's relevant, but a full copy of the related UnitType is viewable as part of newForm only after context.TestForm2s.add(newForm) resolves. This is weird to me since I don't think it should be automatically relating the data object like that.
I haven't been able to try much since everything looks properly configured to me. Please let me know if this is not the case or if I should include any other info.
Found the issue. The vm.Sections was not using viewmodels to contain the section data, so the vm.Sections contained UnitType database models. Since this was instantiated in the controller (before opening the data context in the TestForm2 Create method) EF assumed that these data were new and needed to be added to the UnitType table.
Hope this thread helps someone else running into similar issues.
I use Entity Framework as an ORM for SQL Server.
This is the code inside my POST API:
var group = await _context.Groups.FindAsync(id);
foreach (CalendarRuleGroup calendarRuleGroup in group.CalendarRuleGroups)
{
CalendarRule calendarRule = _context.CalendarRules
.Where(cr => cr.CalendarRuleGroupId == calendarRuleGroup.Id)
.First();
CalendarRule calendarRuleTemp = (CalendarRule)calendarRule.Clone();
_context.CalendarRules.Add(calendarRuleTemp);
}
await _context.SaveChangesAsync();
This is the Clone() function:
public object Clone()
{
CalendarRule clonedRule = (CalendarRule)MemberwiseClone();
clonedRule.Calendar = null;
clonedRule.CalendarId = default;
clonedRule.CalendarRuleGroup = null;
clonedRule.CalendarRuleGroupId = default;
clonedRule.CalendarContexts = null;
clonedRule.Id = default;
List<CalendarRuleCondition> conditions = new List<CalendarRuleCondition>();
foreach (CalendarRuleCondition condition in clonedRule.CalendarRuleConditions)
{
conditions.Add((CalendarRuleCondition)condition.Clone());
}
clonedRule.CalendarRuleConditions = conditions;
return clonedRule;
}
This is the model of CalendarRule:
public class CalendarRule: ICloneable
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartingFrom { get; set; }
public DateTime? Until { get; set; }
public int? Count { get; set; }
public double Duration { get; set; }
public Frequency Frequency { get; set; }
public int Interval { get; set; }
public int Priority { get; set; }
public bool IsWorking { get; set; }
public int Efficiency { get; set; }
public int Cost { get; set; }
public bool AllowedSetup { get; set; }
public int Color { get; set; }
public string Description { get; set; }
public bool? IsActive { get; set; }
public int CalendarId { get; set; }
public int? CalendarRuleGroupId { get; set; }
public virtual Calendar Calendar { get; set; }
public virtual CalendarRuleGroup CalendarRuleGroup { get; set; }
public virtual ICollection<CalendarContext> CalendarContexts { get; set; }
public virtual ICollection<CalendarRuleCondition> CalendarRuleConditions { get; set; }
}
And this is the error that appears when "clonedRule.CalendarRuleConditions" inside the foreach executes:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HMG79T5K5ECK", Request id "0HMG79T5K5ECK:00000002": An unhandled exception was thrown by the application.
System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.DetachedLazyLoadingWarning': An attempt was made to lazy-load navigation 'CalendarRuleConditions' on a detached entity of type 'CalendarRuleProxy'. Lazy-loading is not supported for detached entities or entities that are loaded with 'AsNoTracking'. This exception can be suppressed or logged by passing event ID 'CoreEventId.DetachedLazyLoadingWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
It appears as if the data contained in "CalendarRule.CalendarRuleConditions" has not been loaded. I don't use "AsNoTracking" and there isn't any call to SaveChangesAsync() before the code of the API that i have posted. How can I solve the problem?
This code here doesn't make any sense:
foreach (CalendarRuleCondition condition in clonedRule.CalendarRuleConditions)
{
conditions.Add((CalendarRuleCondition)condition.Clone());
}
clonedRule.CalendarRuleConditions = conditions;
If you are calling this Clone() method from within the entity instance you want to clone, this should be:
foreach (CalendarRuleCondition condition in CalendarRuleConditions)
{
conditions.Add((CalendarRuleCondition)condition.Clone());
}
clonedRule.CalendarRuleConditions = conditions;
Where we go through each condition in this object's lazy loaded CalendarRuleConditions to build a cloned list for the clone. Even then it would be better to eager load the CalendarRuleConditions and only rely on lazy loading as a fall-back.
This question is similar to this other.
I think the problem is in (CalendarRule)MemberwiseClone(), you should create entity with Create (ef) or CreateProxy (efcore) intead new.
I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?
I am using Entity Framework code first with fluent API I have an items table with foreign keys from users and units tables
but when I load the table to ObservableCollection then bind it to a datagrid the table normal column load it's data normally into the datagrid excpet for the foreign keys which show nothing but when i insert a break point to see the data inside the ObservableCollection I can see that every thing from Users and Units table is there
private void MainContentsWindow_ContentRendered(object sender, EventArgs e)
{
using (var db2 = new DataContext())
{
var AllItems2 = new ObservableCollection<Model.Items.Item>(db2.Items);
ItemsDataGrid.ItemsSource = AllItems2;
}
}
Users
public class User
{
public User()
{
Id = Guid.NewGuid();
IsActive = false;
}
public Guid Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public UserGroup Group { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Items.Item> Items { get; set; } = new List<Items.Item>();
}
public enum UserGroup
{
Administrator = 1,
User,
Unknown
}
base
public class NormalBaseModel : CommonBase
{
public NormalBaseModel()
{
Id = new Guid();
CreateDate = DateTime.Now;
EditDate = null;
}
public Guid Id { get; set; }
public string Notes { get; set; }
public virtual User CreateBy { get; set; }
public DateTimeOffset? CreateDate { get; set; }
public virtual User EditBy { get; set; }
public DateTimeOffset? EditDate { get; set; }
}
items
public class Item : NormalBaseModel
{
public string NameAr { get; set; }
public string NameEn { get; set; }
public int? ManualId { get; set; }
public string Barcode { get; set; }
public byte?[] Image { get; set; }
public virtual Unit Unit { get; set; }
public string MadeIn { get; set; }
public bool IsSerail { get; set; }
public bool IsExpire{ get; set; }
}
Here is a test project on Github
https://github.com/ahmedpiosol/psychic-parakeet.git
https://imgur.com/a/zimd4
When you load your items via EF it needs to create new instances of User and Item. Behind the scenes, EF will call the constructor for each new instance. Your problem is in your constructors:
public User()
{
Id = Guid.NewGuid(); // <- here
}
Your constructor reassigns a new ID each time an instance is created, this will break the referential integrity and cause all sorts of other problems.
Your code doesn't know the difference between creating a new User and recreating a User instance from the database.
I suggest removing the assignments from inside your constructor and placing this either in a static Create method or place wherever you are creating a new User or Item.
p.s. WPF is irrelevant to your problem here.
Fluent API needs to specify foreign key in code, something like
modelBuilder.Entity<Items>()
.HasRequired(o => o.User)
.WithMany(c => c.Items)
.HasForeignKey(o => o.UserId);
I am facing the same issue as described in this question. Problem: my method GetAllConferences() returns correctly all the conferences from the DB, but when I return the result to the View from the controller return Ok(tripListVm) inly the first collection item is returned to the client. On the otehr side, by setting to null all the FK references (as pointed out in the SO question above) I can return correctly all the entities to the client, however this does not seem to me the proper way of proceeding.
EDIT: the solution was much simpler than I though. In the code below (I leave it in its original form for others to see it) I was not mapping the FK entities inside the ViewModel to Dto objects, but returning the model entity itself. That was the reason why I needed to null those inner references to make it work. By returning all Dtos objects, it works properly.
I have three entities involved with 1-many relationships:
public class Conference
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Venue> Venues { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
}
public class Venue
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public int? ConferenceId { get; set; }
public Trip Conference { get; set; }
public int? LocationId { get; set; }
public City City { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Conference> Conferences { get; set; }
public ICollection<Venue> Venues { get; set; }
}
In the repository, I have a method that returns the conferences and the related entities (City and Venues):
public IEnumerable<Conference> GetAllConferences()
{
return _context.Conferences
.Include(t => t.Venues)
.Include(t => t.City)
.ToList();
}
In the controller I need to use the following code to return all the results:
var conferences = _repository.GetAllConferences();
if (conferences.Any())
{
var conferenceListVm = trips.ToConferenceVmList();
//Without setting the FK references to null, I can return only the first result of the collection
foreach (var vm in conferenceListVm)
{
foreach (var pm in vm.PoinOfInterests)
{
pm.Trip = null;
}
vm.Location.Conferences = null;
vm.Location.Venues = null;
}
return Ok(conferenceListVm);
}
public static ConferenceViewModel ToConferenceVm(this Conference conference)
{
var confVm = new ConferenceViewModel();
confVm.Name = conference.Name;
confVm.City = conference.City;
confVm.Venues = conference.Venues;
return tripVm;
}
public static IEnumerable<ConferenceViewModel> ToConferenceVmList(this IEnumerable<Conference> conferences)
{
return conferences.Select(c => c.ToConferenceVm()).ToList();
}