Attached entities not being updated - asp.net-mvc-5.1

I am trying to update an entity which but when I call save changes nothing happens. Here's the code how I attached the entity. I am using EF6.1 and MVC5.1 by the way
var entity = db.Entity.Attach(existingEntity);
entity = new Entity(args0, args1, args2)
entity.Id = existingEntity.Id;
db.SaveChanges()
Here's the entity class:
public class Entity : Entity
{
public int Id { get; set; }
public string PropertyA { get; private set; }
public string PropertyB { get; private set; }
public string PropertyC { get; private set; }
}
public Entity(string args0, string args1, string args2)
{
this.PropertyA = args0;
this.PropertyB = args1;
this.PropertyC = args2;
}
Note that I did set the properties setter to private as I have some inner workings within the class. I'm thinking that the reason why EF can't update the entity is because I instantiate a new one though I did set its primary key. I did also try changing the State to Modified but it just running the "update script" but it doesn't really reflect the changes I made on each property
Any possible resolution for this scenario?

I am myself new to MVC and EF. But here goes.
Are you not mixing up your DAL and BLL by using private set?
Also, when you already have the existing entity why cant you just update the properties for entity directly? The way your class has been set up it can only be created, not modified.
Lastly, I think you have to explicitly tell EF that existingEntity has been modified.

var entity = new Entity(args0, args1, args2)
entity.Id = existingEntity.Id;
db.Entity.Attach(entity);
db.SaveChanges();

Related

When using the OData Client for v4 to create/post a new entity we are not getting the auto-generated (auto-incremented) ID/Key back from the service

When using the OData Client (.NET) for v4 to create/post a new entity we are not getting the auto-generated (auto-incremented) ID/Key back from the service. When we create the new entity, we have no ID assigned (it is an 'int' so the value is '0'). After calling SaveChanges the result JSON response has the new auto-assigned id (e.g. '4662'). The issue is that the Entity on the client side still has '0' for its ID (it's not mapped back to the orig. entity).
I also opened the issue on GitHub: https://github.com/OData/odata.net/issues/775
Assemblies affected
Microsoft.Data.OData - v5.7.0
Microsoft.OData.Client - v6.15.0
Microsoft.OData.Core - v6.15.0
Microsoft.OData.Edm - v6.15.0
Reproduce steps
Save a new entity that will automatically have an ID assigned on the server-side (do not set this ID on the client-side)
After saving, check the ID property on your new entity (on the client-side)
Expected result
The newly created (and saved) entity will have the ID updated to match the server-side response (JSON) that came back to the client.
Actual result
JSON response form the server-side has the correct ID, but the Entity on the client-side is never updated with this new information.
In order to support your scenario, some requirements want to be met.
For a full reference, see my sample project at GitHub.
Given a model:
public class Item {
[Key] //define as key
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //used by EF to auto generate Ids and mapping them back on inserts
public int Id { get; set; }
[Required(AllowEmptyStrings = false)]
public string Name { get; set; }
}
An EntityFramwork-DbContext:
public class SampleContext : DbContext {
public SampleContext()
: base("name=SampleContext") {
}
public DbSet<Person> Persons { get; set; }
public DbSet<Item> Items { get; set; }
}
A straight-forward OData-configuration:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Person>("Persons");
builder.EntitySet<Item>("Items");
config.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
and a corresponding ODataController:
public class ItemsController : ODataController {
private readonly SampleContext _Db;
public ItemsController() {
_Db = new SampleContext();
}
...
[ResponseType(typeof(CreatedODataResult<Item>))]
public IHttpActionResult Post(Item p) {
if (!ModelState.IsValid)
return BadRequest(ModelState);
var inserted = _Db.Items.Add(p);
_Db.SaveChanges();
//return entity with any server side changes. This would also work for other DB-generated columns.
return Created(inserted);
}
}
Should produce the desired result:
public void AddingItemTransportsServerGeneratedIdBackToClientSideModel() {
var item = new Item() {
Name = Guid.NewGuid().ToString()
};
var container = new Container(new Uri("http://localhost/ODataAutoId/odata"));
container.AddToItems(item);
container.SaveChanges();
var actual = item.Id;
var unexpected = 0;
Assert.AreNotEqual(unexpected, actual);
}
Your actual implementation might vary or miss some of the requirements. In order to help in your particular scenarion, provide more details, as stated in my comment.

Why must I access the related fields of a user before I can delete them in ASP.NET MVC EF app?

There is a ApplicationUser. They can have multiple TfsAccounts and one TfsToken. A TfsAccount can have multiple TrackedTasks.
public class ApplicationUser : IdentityUser
{
public virtual ICollection<TfsAccount> TfsAccounts { get; set; }
public virtual TfsToken TfsToken { get; set; }
}
public class TfsAccount
{
[ForeignKey("ApplicationUserId")]
public virtual ApplicationUser ApplicationUser { get; set; }
public string ApplicationUserId { get; set; }
public virtual ICollection<TrackedTask> TrackedTasks { get; set; }
}
public class TrackedTask
{
[ForeignKey("TfsAccountId")]
public virtual TfsAccount TfsAccount { get; set; }
public int TfsAccountId { get; set; }
}
Now, I have a method to cancel a subscription for a user and delete them:
[HttpGet]
public async Task<ActionResult> CancelSubscription()
{
var currentUser = UserManager.FindById(User.Identity.GetUserId());
var tfsAccounts = currentUser.TfsAccounts; <---REMOVE THESE
var tfsToken = currentUser.TfsToken; <---TWO LINES AND IT BREAKS
var result = await UserManager.DeleteAsync(currentUser); <---ON THIS LINE
AuthenticationManager.SignOut();
return RedirectToAction("CancellationConfirmed");
}
Here is the error I get in the browser:
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TfsAccounts_dbo.AspNetUsers_ApplicationUserId". The conflict occurred in database "TfsTeamStatus", table "dbo.TfsAccounts", column 'ApplicationUserId'.
The statement has been terminated.
Why do I have to access the related fields on the user before I can delete the user? This works but feels super hacky.
That's super hacky. Correct.
You need to set on delete cascade option to set for that.
If you are using EF Designer, then you can do that using EF Designer as per these steps
Set Cascade on relation in EF designer. This instruct context that all
loaded related entities must be deleted prior to deletion of the
parent entity. If this doesn't happen EF will throw exception because
internal state will detect that loaded childs are not related to any
existing parent entity even the relation is required. I'm not sure if
this happens before execution of delete statement of the parent entity
or after but there is no difference. EF doesn't reload related
entities after executing modifications so it simply doesn't know about
cascade deletes triggered in the database.
Set ON CASCADE DELETE on
relation in database. This will instruct SQL to delete all related
records which were not loaded to context in the time of deleting the
parent.
If you are doing code first approach, you can do that using Fluent API as per [Entity Framework (EF) Code First Cascade Delete for One-to-Zero-or-One relationship
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOptional(a => a.UserDetail)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
}
Now, coming to your point, I am not sure why it works with the two lines when you access TfsAccount and TfsToken, but I guess EF might be setting Modified flag when you accessed it. So while doing SaveChanges it might be deleting those entities. - It's just a guess.

How to return value with E.F. IDbSet<T>

I have ASP.NET MVC 5 web project with E.F. 6.1.3.
I use IDbSet and its method Add to insert new data in my database. I also use context to save changes.
protected IDbSet<T> DbSet { get; set; }
public DbContext Context { get; set; }
private void Insert(T item)
{
this.DbSet.Add(item);
Context.SaveChanges();
}
When i insert new item in database
Is there any equivalent way in this interface to Sql Command.ExecuteScalar ?
In other words i need to get the Id of newly inserted item (my Id is first column and first row in current table).
You dont need ExecuteScalar (but you have to create your POCOs thru context.Set<T>().Create() method no, you dont have to, it works without proxy)
class MyPoco
{
[Key]
public int Id {get;set;}
}
...
var myPoco = context.Set<MyPoco>().Add(context.Set<MyPoco>().Create());
context.SaveChanges();
int newId = myPoco.Id;
However, if you have to have some direct store query, you can use context.ExecuteStoreQuery (its not on IDbSet, but on Context class)
var departments = context.ExecuteStoreQuery<string>
("select Name from Department where DepartmentID < #p0", 5);
EDIT (after you added code :) ):
You can add custom interface (or even base class) to your POCO with Id property:
public interface IId
{
int Id {get;}
}
class MyPoco
: IId
{
[Key]
public int Id {get;set;}
}
and update your Insert code like this:
private int Insert(T item)
where T : IId
{
this.DbSet.Add(item);
Context.SaveChanges();
return item.Id;
}
Note this doesnt work when you create your POCO simply by poco = new Poco() - this way, you give up lot of EF functionality (proxies), you have to use IDbSet<T>.Create() method) It work.
Or keep your item and take Id value after you send it to your Insert(item):
var myPoco = context.Set<MyPoco>().Add(context.Set<MyPoco>().Create());
context.Insert(myPoco);
int newId = myPoco.Id;

Self referencing loop detected when serializing Data models in MVC5 / EF6

I am getting this error "Self referencing loop detected" while serializing using 'Json.NET'
I have a Book model
public class Book
{
public Book()
{
BookPersonMap = new List<BookPersonMap>();
}
public int BookId { get; set; }
public virtual ICollection<BookPersonMap> BookPersonMap { get; private set; }
(And many other virtual Icollections)
}
And this is the BookPerson Mapping class:
public class BookPersonMap
{
public int BookId { get; set; }
public string PersonName { get; set; }
public int PersonTypeId { get; set; }
public virtual Book Book { get; set; } // Foreign keys
public virtual PersonType PersonType { get; set; }
}
When I try to Serialize the Book object it throws:
"Self referencing loop detected for property 'Book' with type 'System.Data.Entity.DynamicProxies.Book_57F0FA206568374DD5A4CFF53C3B41CFDDC52DBBBA18007A896 08A96E7A783F8'. Path 'BookPersonMap[0]'."
I have tried the things suggested in some of the similar posts
Example:
PreserveReferencesHandling = PreserveReferencesHandling.Objects in Serializer settings returned a string with length 3 million!
ReferenceLoopHandling = ReferenceLoopHandling.Ignore in Serializer settings :
"An exception of type 'System.OutOfMemoryException' occurred in Newtonsoft.Json.dll but was not handled in user code"
^ Same luck with "ReferenceLoopHandling.Serialize"
MaxDepth = 1 : Infinite loop again.
Putting [JsonIgnore] on the virtual properties is working but it is a tedious task (because of numerous FK references) and not efficent, since if I miss one property and it will throw exception.
What is missing from above Json settings for them be not working?
services.AddMvc().AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
I have found the best way to solve this type of error is to flatten your model using a view model.
Put a break point on your object before it is serialized and start drilling into the child properties. You will probably find that you can go on indefinitely.
This is what the serializer is choking on.
Create a Constructor for your controller and put on it this line of code :
db.Configuration.ProxyCreationEnabled = false;
//db is the instance of the context.
For asp.net mvc 5 use this
Add the code below to your Application_Start method inside globax.asax file or startup file.
protected void Application_Start()
{
..
GlobalConfiguration.Configuration.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
Disable lazy loading and
ensure your controller does not return
Json(..obj)
rather it should return
Ok(..obj)

DeleteOnNull Error

I've got a set of DB objects sitting in an EntitySet on my main object definition. This handles additions and updates fine, but I found the removing items from the list didn't result in the database records being deleted, so I had to create a method in the data repository object to delete the records as the data object doesn't have access to the data-context in which it is being used.
I was looking to see if I could bring this delete into the main object and I found the DeleteOnNull attribute to the association, but when I use it, I get an error "DeleteOnNull can only be true for singleton association members mapped to non-nullable foreign key columns". My code is:
private EntitySet<UserSite> _userSites = new EntitySet<UserSite>();
[Association(Name = "User_UserSites", Storage = "_userSites", ThisKey = "UserID", OtherKey = "UserID", DeleteOnNull=true)]
public IList<UserSite> UserSites { get { return _userSites; } set { } }
my usersite object is
[Table(Name="UserSite")]
public class UserSite
{
[Column]//(IsPrimaryKey = true)]
public int UserID { get; set; }
[Column]//(IsPrimaryKey = true)]
public string Site { get; set; }
[Column]
public bool DefaultSite { get; set; }
[Column(IsPrimaryKey = true, AutoSync = AutoSync.OnInsert)]
public int UniqueID { get; set; }
}
Can I use DeleteOnNull to keep all my data update methods within my main user object, or do I have to handle the deletes at the repository level?
DeleteOnNull is only for singleton associations. So you can put it on UserSite.User but not on User.UserSites. It's still not quite as automatic as you'd like it to be, though. There is an example here.
It's hard for LINQ to SQL to infer the behavior you want, because it can't guess if you want composition or aggregation, so it chooses the safe guess (aggregation).

Resources