Unable to save off DB changes using Entity Framework - sql

I am pretty new to the EF world and am trying to understand why I am unable to save off changes to my phone record. I realize that I need to modify my entity but it doesn't seem to make a difference. I have tried using context.xxxx but I am getting an error that context is not defined. What is the using for context? If I change the phone type for the same user I want to update the record with the new info.
Phone definition:
namespace NewSMS.DATA.EF
{
using System;
using System.Collections.Generic;
public partial class Phone
{
public int Phone_ID { get; set; }
public string Phone_Number { get; set; }
public int User_PK1 { get; set; }
public int LU_Phone_Type_PK1 { get; set; }
public Nullable<bool> Available { get; set; }
public virtual LU_Phone_Type LU_Phone_Type { get; set; }
public virtual User User { get; set; }
}
}
Code:
if (phnMems != null && phnMems.Count > 0)
{
var smsPhn = new Phone { };
foreach (var item in phnMems)
{
smsPhn.Phone_Number = item.Phone_Number;
smsPhn.LU_Phone_Type_PK1 = item.Phone_Type_ID;
smsPhn.User_PK1 = obj.User_ID;
Phone phoneInfo = db.Phone.FirstOrDefault(n => n.User_PK1 == obj.User_ID);
if (phoneInfo == null)
{
db.Phone.Add(smsPhn);
db.SaveChanges();
}
else
{
smsPhn.Phone_ID = phoneInfo.Phone_ID; //keep userID
db.Entry(phoneInfo).State = EntityState.Modified;
phoneInfo = smsPhn;
db.SaveChanges();
}
}
} //End IF

smsPhn.Phone_ID = phoneInfo.Phone_ID; //keep userID
db.Entry(phoneInfo).State = EntityState.Modified;
In above code, you are assigning phone_ID in smsPhn object but updating phoneInfo object.
Just a Suggestion:
Move this declaration
var smsPhn = new Phone { };
inside foreach loop and call savechanges outside the loop.

Related

How to override the ef core to write data automatically or log to file?

I am using asp.net core + ef core. I can add delete get or update data to the database.
But can it be true that when I do these operations it can auto log to file or database the userinfo and datetime?
If I am updating data the data will auto update the LastModifiedDateTime and ModifyUserId property.
or log it to file?
for example:
If I update the database
await _context.SaveChangesAsync(cancellationToken);
I can do this:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
foreach (var entry in ChangeTracker.Entries<AuditableEntity>())
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.CreatedBy = _currentUserService.UserId;
entry.Entity.Created = _dateTime.Now;
break;
case EntityState.Modified:
entry.Entity.LastModifiedBy = _currentUserService.UserId;
entry.Entity.LastModified = _dateTime.Now;
break;
}
}
return base.SaveChangesAsync(cancellationToken);
}
no need I specify the property but override it to log it automatically?
how to set the property of each entity?
Then I can know when and who do the update?
Create new base model class. Every models must have at least one or more columns that are same such as ID, CreatedBy, etc. Then link base model with all your models.
BaseModel.cs
public class BaseModel
{
public int ID { get; set; }
public DateTime? Created { get; set; }
public string CreatedBy { get; set; }
public DateTime? LastModified { get; set; }
public string LastModifiedBy { get; set; }
}
User.cs
public class User : BaseModel
{
public string Name { get; set; }
public int Age {get; set; }
}
YourDbContext.cs
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseModel && (x.State == EntityState.Added || x.State == EntityState.Modified));
DateTime dt = DateTime.Now;
foreach (var entity in entities)
{
if (entity.State == EntityState.Added)
{
((BaseModel)entity.Entity).CreatedBy = _currentUserService.UserId;
((BaseModel)entity.Entity).Created = dt;
}
else if (entity.State == EntityState.Modified)
{
((BaseModel)entity.Entity).ModifiedBy = _currentUserService.UserId;
((BaseModel)entity.Entity).LastModified = dt;
}
}
return base.SaveChangesAsync(cancellationToken);
}

Retrieve values from SQL database - EF

I'm trying to figure out how to pull values from a SQL database and display this in a razor view.
I have the following class using Entity Framework (I believe)
public class EventLog
{
[Key]
public int Id { get; set; }
public int EventId { get; set; }
public int MaxDelegates { get; set; }
public string Code { get; set; }
public DateTime End { get; set; }
public string Title { get; set; }
}
And I want to map title to DBTitle in the following model:
public class CourseDetailVM : CourseDetailSummaryVM
{
public EventLog DBTitle { get; set; }
}
I then want to see this in the following view:
#using TSW.Web.Helpers
#model TSW.Web.ViewModels.CourseDetailVM
#{
Layout = "~/Views/_Master.cshtml";
}
#Model.DBTitle.Title;
I have the following controller already in place (sorry for the length I plan to reduce this down):
public class CourseDetailController : BaseRenderController<CourseDetailPageDT>
{
private readonly ISitePageFactory _pageFactory = null;
private readonly IEventService _eventService = null;
public CourseDetailController(IEventService eventService, ISitePageFactory pageFactory)
{
_pageFactory = pageFactory;
_eventService = eventService;
}
public async Task<ActionResult> CourseDetail()
{
var homepage = _pageFactory.GetCurrentHomepage();
var model = Mapper.Map<CourseDetailVM>(CurrentContent);
model.Email = homepage.ContactEmail;
model.PhoneNumber = homepage.HeaderPhoneNumber;
model.InnerPageHeader.ShowHeading = true;
model.InnerPageHeader.Title = model.PageTitle;
if (model.Categories.Count == 1)
{
var categoryTagId = model.Categories.First().Id;
var contentTypeAlias = DocumentTypeHelper.GetDocumentTypeAlias<CourseListingPageDT>();
var courseCategoryPage = Umbraco.TypedContentAtXPath($"//{contentTypeAlias}")
.FirstOrDefault(x => x.GetPropertyValue<int>(Constants.DocumentTypes.CourseListingPage.Category) == categoryTagId);
if (courseCategoryPage != null)
{
model.InnerPageHeader.BackLink = Mapper.Map<LinkItem>(courseCategoryPage.Id);
}
}
try
{
model.Events = await _eventService.GetEventsForCourse(CurrentContent.AdministrateId);
}
catch (Exception ex)
{
model.Events = new StaticPagedList<Event>(Enumerable.Empty<Event>(), 1, 1, 0);
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
if (CurrentContent.Graphic != 0)
{
model.InnerPageHeader.Graphic = Mapper.Map<CtaItem>(CurrentContent.Graphic);
}
return View(model);
}
}
I've tried every suggestion I can google to add the mapping in the controlling but can't get my head around this simple function of pulling the value from a SQL database into the razor view.
Could anyone help me out?

Why, in Entity Framework 6, when copying a domain model to another domain model, does the original object get updated when changing the copy's values?

Below are the necessary models for this example.
public class OrderDetailPackageVM
{
public OrderDetail OrderDetail { get; set; }
public Package Package { get; set; }
}
public class Package
{
public Package()
{
this.PackageProducts = new List<PackageProduct>();
}
public int PackageId { get; set; }
public int WebsiteId { get; set; }
public virtual List<PackageProduct> PackageProducts { get; set; }
}
public class PackageProduct
{
public int PackageProductId { get; set; }
public int PackageId { get; set; }
public virtual Package Package { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int ProductCategoryId { get; set; } // not a FK but data only
public virtual ProductCategory ProductCategory { get; set; }
}
In the following code snippet, you should see the problem illustrated.
List<OrderDetailPackageVM> pkgs = (from odx in db.OrderDetails
from pax in db.Packages
where odx.OrderId == orderId
&& pax.PackageId == odx.PackageId
&& odx.PricelistProduct.Product.isStandalone == true
&& pax.WebsiteId == websiteId
select new OrderDetailPackageVM
{
Package = pax,
OrderDetail = odx
}).AsNoTracking().ToList();
List<OrderDetailPackageVM> packages = new List<OrderDetailPackageVM>();
packages.AddRange(pkgs);
//also tried packages = pkgs;
//also tried packages.injectFrom(pkgs) //from omu valueInjector - similar to automapper
At this point in my watch we see:
pkgs.Package.PackageProducts.Count = 6;
packages.Package.PackageProducts.Count = 6;
foreach (OrderDetailPackageVM pac in packages)
{
pac.Package.PackageProducts.RemoveAll();
}
At this point in my watch we see:
pkgs.Package.PackageProducts.Count = 0;
packages.Package.PackageProducts.Count = 0;
When I was expecting to see:
pkgs.Package.PackageProducts.Count = 6;
packages.Package.PackageProducts.Count = 0;
So why is the original object changing when the changes are applied to the copy. I do not remember this behavior in earlier versions of EF?
And what is the work-around for this?
I thought doing a select with NoTracking was supposed to 'Free' the data in the model from EF change tracking?
Thanks so much for helping me understand this behavior.
THE FOLLOWING IS THE METHOD I USED TO SOLVE THIS ISSUE BASED ON FEEDBACK BELOW:
public static T DeepClone<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source));
}
You do not create new objects. You place the existing objects in a new list. You would need to create completely new objects and copy the values manually. This is also known as a deep copy or doing a clone ( see ICloneable ).
If omu valueInjector assigns property for property it assigns the list of object a to the list of object b. As it is a reference type, it's actually the same. If you want to have new objects you have to make a deep copy. See Deep cloning objects for more info.
The behavior has actually nothing with tracking the changes from an EF view. You work with reference types.
A small sample program:
using System;
using System.Collections.Generic;
namespace _28637405 {
class Outer {
public string MyProperty { get; set; }
}
class Program {
static void Main( string[] args ) {
var listOne = new List<Outer>();
for ( int i = 0; i < 10; i++ ) {
listOne.Add( new Outer { MyProperty = "obj #" + (i + 1) } );
}
// first line
Console.WriteLine( listOne[0].MyProperty );
var listTwo = new List<Outer>();
listTwo.AddRange( listOne );
// second line
Console.WriteLine( listTwo[0].MyProperty );
listTwo[0].MyProperty = "Changed";
// third and fourth line
Console.WriteLine( listOne[0].MyProperty );
Console.WriteLine( listTwo[0].MyProperty );
var listThree = new List<Outer>();
foreach ( var obj in listOne )
listThree.Add( new Outer { MyProperty = obj.MyProperty } );
listThree[0].MyProperty += " again";
// lines 5,6,7
Console.WriteLine( listOne[0].MyProperty );
Console.WriteLine( listTwo[0].MyProperty );
Console.WriteLine( listThree[0].MyProperty );
}
}
}
The ouutput it produces:
obj #1
obj #1
Changed
Changed
Changed
Changed
Changed again
The class Outer would look like this if it would implement ICloneable:
class Outer : ICloneable {
public string MyProperty { get; set; }
public object Clone() {
return new Outer { MyProperty = this.MyProperty };
}
}
Usage would be (including a cast to Outer ):
var newObject = existingObject.Clone() as Outer;

Can't get ASP.Net MVC Edit Action to worth with my ViewModel

I am trying to use the following ViewModel
public class ProjectViewModel
{
public Project Project { get; set; } //bulk of the information
public int SelectedGovernmentClientId { get; set; } //selected ID for the dropdown
public IEnumerable<SelectListItem> GovernmentClients { get; set; } //dropdown values
}
Here's my project class
public class Project
{
public int ID { get; set; }
public string Title { get; set; }
//omitting extra fields
public virtual GovernmentClient GovernmentClient { get; set; }
}
Here's the action that I have
[HttpPost]
public ActionResult Edit(ProjectViewModel projectViewModel)
{
if (ModelState.IsValid)
{
//i am getting the following from debugging
//projectViewModel.Project.GovernmentClient.Name is NULL
//projectViewModel.Project.GovernmentClient.ID is the correct ID from the dropdown
db.Entry(projectViewModel.Project).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(projectViewModel);
}
All the values are getting updated EXCEPT the government client. Why is this happening?
projectViewModel.Project.GovernmentClient =
db.GovernmentClients.Find(projectViewModel.SelectedGovernmentClientId);
You are retrieving the value from database and setting its state to modified. However, there is no modification at all.
After a whole day of playing around with this, I think I might've solved it
I added the following to my Project model
[ForeignKey("GovernmentClient")]
public int GovernmentClient_ID { get; set; }
To render the dropdown, I used this
#Html.DropDownListFor(model => model.Project.GovernmentClient_ID, Model.GovernmentClients)
To generate the GovernmentClients list, I used the following method
private IEnumerable<SelectListItem> GetGovernmentClientsList(int selectedItem = -1)
{
var defaultItem = Enumerable.Repeat(new SelectListItem
{
Value = "-1",
Text = " - Select a government client - ",
Selected = (selectedItem == -1)
}, count: 1);
var clients = db.GovernmentClients.ToList().Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.ClientName,
Selected = (selectedItem == -1) ? false : (x.ID == selectedItem)
});
return defaultItem.Concat(clients);
}
Overall, I am happy with this, because I am not hardcoding any property names, which I know would come back and bite me.

Objects not getting saved properly in Database

[Test]
public void Artist_gets_stored_properly()
{
using (ISession session = NHHelper.GetSession())
{
Artist artist = new Artist() { Name = "Somathilaka Jayamaha" };
artist.Songs = new List<Song>()
{
new Song(){Artist = artist, Name = "Manamaala girawu"},
new Song(){Artist = artist, Name = "Sende andura"},
new Song(){Artist = artist, Name = "Sunilwan nuwan"}
};
foreach (var s in artist.Songs)
{
session.Save(s);
}
session.Save(artist);
}
using (ISession session = NHHelper.GetSession())
{
Artist artist = session.Query<Artist>().Single(x => x.Name == "Somathilaka Jayamaha");
Assert.AreEqual(3, artist.Songs.Count);
Assert.AreEqual("Sende andura", artist.Songs[1].Name);
}
}
public class Artist
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Song> Songs { get; set; }
}
public class Song
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string WebPath { get; set; }
public virtual string FilePath { get; set; }
public virtual bool Downloaded { get; set; }
public virtual Artist Artist { get; set; }
void Download(IDownloader downloader)
{
}
}
I have the above test case and it fails at this line : Assert.AreEqual(3, artist.Songs.Count);. The songs do not get saved it seems. I use automapping and have Cascade.All() for the collection fields in mapping overrides and have lazyloading off. I can't understand why this test fails. As you can see, I manually saved the 3 songs as well though as I understand it, I don't need to do that when I have Cascade.All() for Artist.Songs field. Can someone tell me what I am doing wrong? Thanks.
MS SQLServer 2005, .NET 3.5, FluentNHibernate 1.2.0.712
You are not using a transaction, and you are never flushing your session.
The only reason you are getting the inserts is because you are using an identity generator, which inserts when you call Save (this is a limitation, not a feature).
The correct way to to this:
using (ISession session = NHHelper.GetSession())
using (var transaction = session.BeginTransaction())
{
Artist artist = new Artist { Name = "Somathilaka Jayamaha" };
artist.Songs = new List<Song>()
{
new Song{Artist = artist, Name = "Manamaala girawu"},
new Song{Artist = artist, Name = "Sende andura"},
new Song{Artist = artist, Name = "Sunilwan nuwan"}
};
session.Save(artist);
transaction.Commit();
}