I have an entity (I am using code first) that looks like that:
public class Node
{
public int ID { get; set; }
public string SomeInfo { get; set; }
public virtual Node Previous { get; set; }
public virtual Node Next { get; set; }
}
There is no problem to save the Next Node for example. However if the ID of Previous is 1 and I try to set the Next Node (wich is the one with ID=1) to 2 this exception is thrown.
The object cannot be added to the object context. The object�s
EntityKey has an ObjectStateEntry that indicates that the object is
already participating in a different relationship.
I am saving the node like this:
int nextId;
int previousId;
if (int.TryParse(Request["previous"], out previousId))
node.Previous = this.nodeRepository.GetSingle(previousId);
if (int.TryParse(Request["next"], out nextId))
node.Next = this.nodeRepository.GetSingle(nextId);
this.nodeRepository.Update(node);
Update looks like this:
public virtual void Update(T entity)
{
this.context.Entry(GetSingle(entity.ID)).State = EntityState.Detached;
this.context.Entry(entity).State = EntityState.Added;
this.context.Entry(entity).State = EntityState.Modified;
this.Save();
}
And GetSingle like this:
public virtual T GetSingle(object id)
{
var query = this.entities.Find(id);
return query;
}
UPDATE 1
The line with the exception is in the Update method:
this.context.Entry(entity).State = EntityState.Modified;
Is your context in a Using block and being disposed of at some point? I've had similar 'The object cannot be added to the object context." errors. I would add Id's for Previous and Next to your model and use those to update the foreign keys.
public class Node
{
public int ID { get; set; }
public string SomeInfo { get; set; }
public virtual Node Previous { get; set; }
public int PreviousId { get; set; }
public virtual Node Next { get; set; }
public int NextId { get; set; }
}
To update the foreign keys...
int nodeId; // I'm assuming you know the id of node you want updated.
int nextId;
int previousId;
using (var context = new Context())
{
// Perform data access using the context
var node = context.Nodes.find(nodeId);
node.NextId = nextId;
node.PreviousId = previousId;
context.SaveChanges();
}
Related
I search for a way to combine two or more IQueryables from different Object types in order to use it as a datasource for my treelist.
For the treelist I use the DevExpress WinForms component "TreeList".
It provides me the properties "KeyFieldName" which is usually mapped to the "ID" and the ParentFieldName which is mapped to the parent id in order to build a hierarchy.
I use entity framework 6 as or mapper.
I have the two following classes I would need to combine:
XObject:
[Table("tbl_objects")]
public class XObject
{
[Column("id")]
public int Id { get; set; }
[Column("display_name")]
public String DisplayName { get; set; }
[Column("description")]
public String Description { get; set; }
[Column("usage_reason")]
public String UsageReason { get; set; }
[Column("is_network_compatible")]
public bool IsNetworkCompatible { get; set; }
[Column("ip_address")]
public String IpAddress { get; set; }
[Column("network_name")]
public String NetworkName { get; set; }
[Column("serial_number")]
public String SerialNumber { get; set; }
[Column("manufacturer_identification_code")]
public String ManufacturerIdentificationCode { get; set; }
[Column("web_link")]
public String WebLink { get; set; }
[Column("warranty")]
public int WarrantyInDays { get; set; }
[Column("ref_manufacturer")]
public virtual XManufacturer Manufacturer { get; set; }
[Column("ref_order")]
public virtual XOrder Order { get; set; }
[Column("ref_owner")]
public virtual XOwner Owner { get; set; }
[Column("ref_room")]
public virtual XRoom Room { get; set; }
[Column("ref_object_folder")]
public virtual XObjectFolder ObjectFolder { get; set; }
public virtual ICollection<XAdditionalObjectData> AdditionalObjectData { get; set; }
}
XObjectFolder:
[Table("tbl_object_folders")]
public class XObjectFolder
{
[Column("id")]
public int Id { get; set; }
[Column("display_name")]
public String DisplayName { get; set; }
[Column("short_name")]
public String ShortName { get; set; }
[Column("ref_parent_folder")]
public virtual XObjectFolder ParentFolder { get; set; }
public virtual ICollection<XObjectFolder> ChildFolders { get; set; }
public virtual ICollection<XObject> Objects { get; set; }
[NotMapped]
public int ParentFolderId { get { return ParentFolder == null ? -1 : ParentFolder.Id; } }
}
As you've probably already seen, an object folder can contain subfolders but also objects.
My goal is to see this as one "datasource" in my treelist.
For example like this:
Object Folder A
Object Sub-Folder A
Object 1
Object 1
In other questions here I've found the possibilities to concat or union queryables, but that only works with them being the same type:
using (var db = new XDbContext(_conString))
{
// Queryables
var ofs = from of in db.ObjectFolders orderby of.DisplayName ascending select of; // <- All ObjectFolders
var obs = from obj in db.Objects orderby obj.DisplayName ascending select obj; // <- All Objects
// Concat them
var comb = ofs.Concat(obs); // <- not the same type
// As DataSource for my TreeList
TreeListObjects.DataSource = comb.ToList();
}
Which is why I am searching for a good way to make this possible.
I could also imagine me using a pretty bad approach to reach my goal. So I am open to suggestions. This is a personal project which I do to improve myself at stuff.
Thanks in advance!
EDIT
So I managed to get a step further by using an interface both classes share:
public interface ITreeListCombinable
{
int Id { get; set; }
int ParentId { get; }
String DisplayName { get; set; }
}
But... who would've thought... there occures another problem:
Have a look at the db structure:
Db_Struture
Since both objects are stored in different tables, the id's will certainly not be unique when combining them.
Which is necessary when setting the datasource.
Solution:
So I've taken my own approach to my problem and it worked out.
Full disclosure -> I consider myself a beginner, so this solution is probably not the best. Still, if anyone is in a similar situation, here's how it could work:
First I created an interface, which both the folder and objects share:
ITreeListCombinable
public interface ITreeListCombinable
{
int Id { get; set; }
int ParentId { get; }
int ListId { get; set; }
int ParentListId { get; set; }
String DisplayName { get; set; }
ObjectTreeListElementTypes TreeListElementType { get; }
}
I then made sure, both my XObject and XObjectFolder classes held the ObjectTreeListElementTypes value they're corresponding to:
ObjectTreeListElementTypes Enum:
public enum ObjectTreeListElementTypes
{
Folder,
Object
}
Classes:
[NotMapped]
public ObjectTreeListElementTypes TreeListElementType => ObjectTreeListElementTypes.Folder; // or *.Object for that matter
So afterwards I've wrote my own "controller" which handles my specific scenario.
ObjectTreeListElementController:
public class ObjectTreeListElementController
{
private List<ITreeListCombinable> _list;
public ObjectTreeListElementController()
{
_list = new List<ITreeListCombinable>();
}
public void AddRange(List<ITreeListCombinable> list)
{
// add incoming items to private _list
_list.AddRange(list);
}
public List<ITreeListCombinable> GetDataSourceList()
{
// create auto increment list id
var listId = 0;
foreach (var item in _list)
{
item.ListId = listId;
listId++;
}
// set new parent list id according to incremental list id
foreach (var item in _list)
{
var parents = _list.Where(x => x.Id == item.ParentId && x.TreeListElementType == ObjectTreeListElementTypes.Folder);
if (parents.Count() > 0)
item.ParentListId = parents.First().ListId;
else
item.ParentListId = -1;
}
return _list;
}
}
Essentially, when calling the GetDataSourceList() method, it firstly distributes incremental, temporary list-ids.
In a second loop I then search for the original parent id and match the tree list element type. If none is found, this folder is a root folder in my treelist, if one is found, the given list-id becomes the parent list id:
using (var db = new XDbContext(_conString))
{
// Queryables
IQueryable<ITreeListCombinable> ofs = from of in db.ObjectFolders orderby of.DisplayName ascending select of;
IQueryable<ITreeListCombinable> objs = from obj in db.Objects orderby obj.DisplayName ascending select obj;
var lofs = ofs.ToList();
var lobjs = objs.ToList();
var ctrl = new ObjectTreeListElementController();
ctrl.AddRange(lofs);
ctrl.AddRange(lobjs);
var sourceList = ctrl.GetDataSourceList();
// As DataSource for my TreeList
TreeListObjects.DataSource = sourceList;
}
And this brought me the exact output I've wanted:
Hope this helps another beginner :)
Please consider the following entities
public class What {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Track> Tracks { get; set; }
public int? LastTrackId { get; set; }]
public Track LastTrack { get; set; }
}
public class Track {
public Track(string what, DateTime dt, TrackThatGeoposition pos) {
What = new What { Name = what, LastTrack = this };
}
public int Id { get; set; }
public int WhatId { get; set; }
public What What { get; set; }
}
I use the following to configure the entities:
builder.HasKey(x => x.Id);
builder.HasMany(x => x.Tracks).
WithOne(y => y.What).HasForeignKey(y => y.WhatId);
builder.Property(x => x.Name).HasMaxLength(100);
builder.HasOne(x => x.LastTrack).
WithMany().HasForeignKey(x => x.LastTrackId);
Has you can see there is a wanted circular reference:
What.LastTrack <-> Track.What
when I try to add a Track to the context (on SaveChanges in fact):
Track t = new Track("truc", Datetime.Now, pos);
ctx.Tracks.Add(t);
ctx.SaveChanges();
I get the following error:
Unable to save changes because a circular dependency was detected in the data to be saved: ''What' {'LastTrackId'} -> 'Track' {'Id'}, 'Track' {'WhatId'} -> 'What' {'Id'}'.
I would like to say... yes, I know but...
Is such a configuration doable with EF Core ?
This is what I like to call the favored child problem: a parent has multiple children, but one of them is extra special. This causes problems in real life... and in data processing.
In your class model, What (is that a sensible name, by the way?) has Tracks as children, but one of these, LastTrack is the special child to which What keeps a reference.
When both What and Tracks are created in one transaction, EF will try to use the generated What.Id to insert the new Tracks with WhatId. But before it can save What it needs the generated Id of the last Track. Since SQL databases can't insert records simultaneously, this circular reference can't be established in one isolated transaction.
You need one transaction to save What and its Tracks and a subsequent transaction to set What.LastTrackId.
To do this in one database transaction you can wrap the code in a TransactionScope:
using(var ts = new TransactionScope())
{
// do the stuff
ts.Complete();
}
If an exception occurs, ts.Complete(); won't happen and a rollback will occur when the TransactionScope is disposed.
I encountered the same problem, but i solved it differently.
In my case, it was about a list of status and a reference to the last status. So with the following case :
public class What {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Status> StatusList { get; set; }
public int? LastStatusId { get; set; }
public Status LastStatus { get; set; }
public void AddStatus(Status s)
{
StatusList.Add(s);
LastStatus = s;
}
}
public class Status{
public int Id { get; set; }
public int WhatId { get; set; }
public What What { get; set; }
}
In my program, i changed my code to use StatusList as an history that doesn't include the lastStatus, so :
public class What {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Status> StatusHistory { get; set; }
public int? LastStatusId { get; set; }
public Status LastStatus { get; set; }
public void AddStatus(Status s)
{
if(LastStatus) StatusList.Add(LastStatus);
LastStatus = s;
}
public List<Status> GetStatusList(Status s) // If needed, a method, not a property because i got an error with lazyLoading
{
return new List<Status>(StatusHistory) { LastStatus}; // List of all status (history + last)
}
}
public class Status{
public int Id { get; set; }
public int? WhatId { get; set; }
public What What { get; set; }
}
and don't forget to put in your context IsRequired(false) on the foreignKey :
builder.HasMany(x => x.Status).
WithOne(y => y.What).HasForeignKey(y => y.WhatId).IsRequired(false);
Like this, no more circular reference.
I have two related Entity Framework 6 classes in my data layer.
public class Order
{
public int Id { get; set; }
public virtual SalesStatus SalesStatus { get; set; }
}
public class SalesStatus
{
public SalesStatus()
{
Orders = new List<Order>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Order> Orders { get; set; }
}
public class OrderVM
{
public int Id { get; set; }
public SalesStatus SalesStatus { get; set; }
}
I am using Automapper to map these to my view models and back again.
cfg.CreateMap<Order, OrderVM>()
.MaxDepth(4)
.ReverseMap();
The status entity is used to populate a drop down list.
In my method I am taking the selected value and trying to update the order record to the new selected status.
private bool SaveOrderToDb(OrderVM orderVM)
{
using (var db = new MyContext())
{
var order = AutomapperConfig.MapperConfiguration.CreateMapper().Map<OrderVM, Order>(orderVM);
order.SalesStatus = db.SalesStatuses.Find(Convert.ToInt16(orderVM.SalesStatusSelectedValue));
db.Set<Order>().AddOrUpdate(order);
db.SaveChanges();
}
return true;
}
This does not update the relationship in the database. Why? What am I missing?
I get an error when I try to insert a value in my Table.
_dltype is an object of type BRIDownloadType.
using (var db = new BRIDatabase())
{
foreach (var client in db.BRIClients)
{
var todo = new BRIToDo
{
BRIClient = client,
BRIDownloadType = _dltype,
};
db.BRIToDos.Add(todo);
}
db.SaveChanges();
}
Now I get the error:
An Explict Value can't be inserted in the Idendity Column in the BRIDownloadTypes-Table when IDENTITY_INSERT is OFF.
My 2 Tables are
BRIDownloadType
public class BRIDownloadType
{
[Key]
public int DlTypeId { get; set; }
[Required]
[StringLength(15)]
public string DlType { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public virtual ICollection<BRIToDo> BRIToDo { get; set; }
}
BRITodo
public class BRIToDo
{
[Key]
public int ToDoId { get; set; }
[ForeignKey("BRIClient")]
public int ClientId { get; set; }
[ForeignKey("BRITask")]
public int TaskId { get; set; }
public virtual BRIClient BRIClient { get; set; }
public virtual BRITask BRITask { get; set; }
[ForeignKey("BRIDownloadType")]
public int DlTypeId { get; set; }
public virtual BRIDownloadType BRIDownloadType { get; set; }**
}
The interesting thing is, if I do something with my _dltype object, I can use it.
The following code is working and I don't understand why, I'm inserting the exact same object.
using (var db = new BRIDatabase())
{
var dl = db.BRIDownloadTypes.FirstOrDefault(c => c.DlTypeId == _dltype.DlTypeId);
foreach (var client in db.BRIClients)
{
var todo = new BRIToDo
{
BRIClient = client,
BRIDownloadType = _dltype,
};
db.BRIToDos.Add(todo);
}
db.SaveChanges();
}
Can anybody explain to me, why the last approach is working and the first is throwing that error? I just added the line
var dl = db.BRIDownloadTypes.FirstOrDefault(c => c.DlTypeId == _dltype.DlTypeId)
But I'm still inserting the same object. If I insert the Id of the object instead of the object it is also working fine. I have no idea whats going on there.
Assume that I have the following little console application which uses Entity Framework 5:
class Program {
static void Main(string[] args) {
using (var ctx = new ConfContext()) {
var personBefore = ctx.People.First();
Console.WriteLine(personBefore.Name);
personBefore.Name = "Foo2";
ctx.SaveChanges();
var personAfter = ctx.People.First();
Console.WriteLine(personAfter.Name);
}
Console.ReadLine();
}
}
public class ConfContext : DbContext {
public IDbSet<Person> People { get; set; }
public IDbSet<Session> Sessions { get; set; }
}
public class Person {
[Key]
public int Key { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime? BirthDate { get; set; }
public ICollection<Session> Sessions { get; set; }
}
public class Session {
[Key]
public int Key { get; set; }
public int PersonKey { get; set; }
public string RoomName { get; set; }
public string SessionName { get; set; }
public Person Person { get; set; }
}
As you can see, I am changing the name of the record and saving it. It works but it feels like magic to me. What I am doing in all of my applications is the following one (to be more accurate, inside the Edit method of my generic repository):
static void Main(string[] args) {
using (var ctx = new ConfContext()) {
var personBefore = ctx.People.First();
Console.WriteLine(personBefore.Name);
personBefore.Name = "Foo2";
var entity = ctx.Entry<Person>(personBefore);
entity.State = EntityState.Modified;
ctx.SaveChanges();
var personAfter = ctx.People.First();
Console.WriteLine(personAfter.Name);
}
Console.ReadLine();
}
There is no doubt that the second one is more semantic but is there any other obvious differences?
Well the second code block where you explicitly set the entity state is redundant, as the change tracker already knows that the entity is modified because the context knows about the entity (as you query the context to retrieve the entity).
Setting (or painting) the state of the entity would be more useful when working with disconnected entities, for example in an n-tier environment where the entity was retrieved in a different context and sent to a client for modification, and you wish to mark those changes back on the server using a different context.
Otherwise, the first code block is cleaner in my opinion.