I am trying to use Linq to Entity as my DAL for a new project.
In my DAL I have this method to get the Job Entity
public Job LoadJob(int id)
{
Job job = new Job();
using (TNEntities context = new TNEntities())
{
var jobEntity = (from c in context.Jobs
where c.id == id
select c).First();
job = (Job)jobEntity;
}
return job;
}
I use the Job entity in my program and then I would like to save it:
I have tried a few different things, but currenlt my method looks like this (it does not work)
public void SaveJob(Job job)
{
using (TNEntities context = new TNEntities())
{
context.Jobs.AddObject(job);
context.SaveChanges();
}
}
I have tried context.jobs.attach(job) which does not throw an error but it does not update my Job. I assume its because the Job is out of Context as its out of scope of the using context. But Im not sure how to re-attach it so it updates the job that I selected in my first method.
Ideally, you want to read your job from a context, make changes, then call SaveChanges on the same context you originally read it from. It might (should) be possible to attach the modified entity to a new context, and set its status to modified, but I've found modified child objects are mistreated with this approach.
One of the easier approaches is to have all of these operations in a DataAccess object that has one instance of your TNEntities context, and uses it to read your job entity, and save changes.
public class JobDao : IDisposable {
TNEntities context = new TNEntities();
public Job LoadJob(int id)
{
return this.context.Jobs.First(c => c.id == id);
}
public void Save(){
this.context.SaveChanges();
}
public void Dispose(){
this.Context.Dispose();
}
}
(of course you'll want to put a lot of that boilerplate code into a base class)
using(JobDao dao = new JobDao()) {
Job j = dao.LoadJob(12);
j.JobTitle = "Software Developer";
dao.Save();
}
I would suggest you consider leaving a property in your application.xaml.cs like
public TNEntities context {get; private set;}
which has its TNEntities initalized at startup. It will make your life easier.
Related
This is my example app scenario:
public class SampleDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=ExampleUserDB;Trusted_Connection=True;");
}
}
public class User
{
public int Id { get; set; }
public bool AnalyseInProgress { get; set; }
public bool Analysed { get; set; }
}
public class UserService
{
public User GetUser()
{
using (var context = new SampleDbContext())
{
using (var transaction = context.Database.BeginTransaction())
{
var entity = context.Users
.Where(x => !x.Analysed)
.Where(x => !x.AnalyseInProgress)
.FirstOrDefault();
entity.AnalyseInProgress = true;
context.SaveChanges();
return entity;
transaction.Commit();
}
}
}
public void MarkUserAsAnalysed(int id)
{
using (var context = new SampleDbContext())
{
var entity = context.Users
.Find(id);
entity.Analysed = true;
context.SaveChanges();
}
}
}
By adding transaction and AnalyseInProgress property, I wanted to prevent returning the same user to multiple applications, if they call UserService.GetUser at the same time.
Unfortunately, it doesn't work as I expected. Sometimes, service returns the same user. Is there any built-in solution to that?
Thanks.
To ensure that two connections across systems cannot end up assessing the same row as available, you need to employ pessimistic locking on the connection when reading. Note that pessimistic locking when used extensively will lead to performance issues, so your focus should be to only use it where absolutely necessary and for as little time as needed. Get in, get done, get out.
public User GetUser()
{
User user = null;
var txOptions = New TransactionOptions {IsolationLevel = System.Transactions.IsolationLevel.Serializable};
using(var txScope = new TransactionScope(TransactionScopeOption.Required, txOptions))
{
using (var context = new SampleDbContext())
{
user = context.Users
.OrderBy(x => x.CreatedAt)
.FirstOrDefault(x => !x.Analysed && !x.AnalyseInProgress);
if(user != null)
{
user.AnalyseInProgress = true;
context.SaveChanges();
context.Entry(user).State = EntityState.Detached;
}
}
txScope.Complete();
}
return user;
}
So a couple things here. First we use an isolation level of Serializable to ensure that only one operation can read this user row if it is finalized. Multiple calls to GetUser, such as coming from multiple web servers will wait if one of the other running calls is still within that Tx Scope. When returning entities outside of the scope of the DbContext that read them then it is best to ensure that they are marked as Detached. Normally you would read these AsNoTracking but since we want to update that flag on them first, we read the entity as a tracked instance, perform the update, then detach it before returning. In your original code the transaction.Commit() would never run because it was after your return statement. (intellisense would be highlighting that.)
When using FirstOrDefault like that you should specify an OrderBy clause, and you need to handle the possibility that no record is returned. The original code would have thrown a NullReferenceException the minute all unprocessed users was achieved.
I don't recommend passing detached entities around, but rather DTOs or ViewModels to avoid confusion around whether a method accepting an entity is getting a tracked, real entity, or getting a detached, or worse, partially filled entity-like class.
I would also recommend considering using an enumeration or such to represent the User's "Status" or such rather than individual flags like "IsProcessing" "IsProcessed". For instance using an enumeration for "Unprocessed, Processing, Processed" means you can simplify the logic to:
user = context.Users
.OrderBy(x => x.CreatedAt)
.FirstOrDefault(x => x.Status == UserStatuses.Unprocessed);
then:
user.Status = UserStatuses.Processing;
It can save accidentally forgetting to append combinations of flags to Where type queries.
Following is the action that is adding a Loan request to the database:
[HttpPost]
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
loanViewModel.LoanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId); // <-- don't want to add to this table in database
loanViewModel.Borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId); //<-- don't want to add to this table in database
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loanService.AddNewLoan(loan);
return RedirectToAction("Index");
}
Following is the AddNewLoan() method:
public int AddNewLoan(Models.Loans.Loan loan)
{
loan.LoanStatus = Models.Loans.LoanStatus.PENDING;
_LoanService.Insert(loan);
return 0;
}
And here is the code for Insert()
public virtual void Insert(TEntity entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
entity.DateCreated = entity.DateUpdated = DateTime.Now;
entity.CreatedBy = entity.UpdatedBy = GetCurrentUser();
Entities.Add(entity);
context.SaveChanges();
}
catch (DbUpdateException exception)
{
throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
}
}
It is adding one row successfully in Loans table but it is also adding rows to LoanProduct and Borrower table as I showed in first code comments.
I checked the possibility of multiple calls to this action and Insert method but they are called once.
UPDATE
I am facing similar problem but opposite in functioning problem here: Entity not updating using Code-First approach
I think these two have same reason of Change Tracking. But one is adding other is not updating.
The following code seems a bit odd:
var loanViewModel = loanEditorViewModel.LoanViewModel;
loanViewModel.LoanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId); // <-- don't want to add to this table in database
loanViewModel.Borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId); //<-- don't want to add to this table in database
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
You are setting entity references on the view model, then calling automapper. ViewModels should not hold entity references, and automapper should effectively be ignoring any referenced entities and only map the entity structure being created. Automapper will be creating new instances based on the data being passed in.
Instead, something like this should work as expected:
// Assuming these will throw if not found? Otherwise assert that these were returned.
var loanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
Edit:
The next thing to check is that your Services are using the exact same DbContext reference. Are you using Dependency Injection with an IoC container such as Autofac or Unity? If so, make sure that the DbContext is set registered as Instance Per Request or similar lifetime scope. If the Services effectively new up a new DbContext then the LoanService DbContext will not know about the instances of the Product and Borrower that were fetched by another service's DbContext.
If you are not using a DI library, then you should consider adding one. Otherwise you will need to update your services to accept a single DbContext with each call or leverage a Unit of Work pattern such as Mehdime's DbContextScope to facilitate the services resolving their DbContext from the Unit of Work.
For example to ensure the same DbContext:
using (var context = new MyDbContext())
{
var loanProduct = LoanProductService.GetLoanProductById(context, loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(context, loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
LoanService.AddNewLoan(context, loan);
}
If you are sure that the services are all provided the same DbContext instance, then there may be something odd happening in your Entities.Add() method. Honestly your solution looks to have far too much abstraction around something as simple as a CRUD create and association operation. This looks like a case of premature code optimization for DRY without starting with the simplest solution. The code can more simply just scope a DbContext, fetch the applicable entities, create the new instance, associate, add to the DbSet, and SaveChanges. There's no benefit to abstracting out calls for rudimentary operations such as fetching a reference by ID.
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var context = new AppContext())
{
var loanProduct = context.LoanProducts.Single(x => x.LoanProductId ==
loanViewModel.LoanProductId);
var borrower = context.Borrowers.Single(x => x.BorrowerId == loanViewModel.BorrowerId);
var loan = AutoMapper.Mapper.Map<Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
context.SaveChanges();
}
return RedirectToAction("Index");
}
Sprinkle with some exception handling and it's done and dusted. No layered service abstractions. From there you can aim to make the action test-able by using an IoC container like Autofac to manage the Context and/or introducing a repository/service layer /w UoW pattern. The above would serve as a minimum viable solution for the action. Any abstraction etc. should be applied afterwards. Sketch out with pencil before cracking out the oils. :)
Using Mehdime's DbContextScope it would look like:
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var contextScope = ContextScopeFactory.Create())
{
var loanProduct = LoanRepository.GetLoanProductById( loanViewModel.LoanProductId).Single();
var borrower = LoanRepository.GetBorrowerById(loanViewModel.BorrowerId);
var loan = LoanRepository.CreateLoan(loanViewModel, loanProduct, borrower).Single();
contextScope.SaveChanges();
}
return RedirectToAction("Index");
}
In my case I leverage a repository pattern that uses the DbContextScopeLocator to resolve it's ContextScope to get a DbContext. The Repo manages fetching data and ensuring that the creation of entities are given all required data necessary to create a complete and valid entity. I opt for a repository-per-controller rather than something like a generic pattern or repository/service per entity because IMO this better manages the Single Responsibility Principle given the code only has one reason to change (It serves the controller, not shared between many controllers with potentially different concerns). Unit tests can mock out the repository to serve expected data state. Repo get methods return IQueryable so that the consumer logic can determine how it wants to consume the data.
Finally with the help of the link shared by #GertArnold Duplicate DataType is being created on every Product Creation
Since all my models inherit a BaseModel class, I modified my Insert method like this:
public virtual void Insert(TEntity entity, params BaseModel[] unchangedModels)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
try
{
entity.DateCreated = entity.DateUpdated = DateTime.Now;
entity.CreatedBy = entity.UpdatedBy = GetCurrentUser();
Entities.Add(entity);
if (unchangedModels != null)
{
foreach (var model in unchangedModels)
{
_context.Entry(model).State = EntityState.Unchanged;
}
}
_context.SaveChanges();
}
catch (DbUpdateException exception)
{
throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
}
}
And called it like this:
_LoanService.Insert(loan, loan.LoanProduct, loan.Borrower);
By far the simplest way to tackle this is to add the two primitive foreign key properties to the Loan class, i.e. LoanProductId and BorrowerId. For example like this (I obviously have to guess the types of LoanProduct and Borrower):
public int LoanProductId { get; set; }
[ForeignKey("LoanProductId")]
public Product LoanProduct { get; set; }
public int BorrowerId { get; set; }
[ForeignKey("BorrowerId")]
public User Borrower { get; set; }
Without the primitive FK properties you have so-called independent associations that can only be set by assigning objects of which the state must be managed carefully. Adding the FK properties turns it into foreign key associations that are must easier to set. AutoMapper will simply set these properties when the names match and you're done.
Check Models.Loans.Loan?Is it a joined model of Loans table , LoanProduct and Borrower table.
You have to add
Loans lentity = new Loans()
lentity.property=value;
Entities.Add(lentity );
var lentity = new Loans { FirstName = "William", LastName = "Shakespeare" };
context.Add<Loans >(lentity );
context.SaveChanges();
This seems arbitrary to me when I have to actually .Include() related entities and when I don't. In some cases, EF gives me the info for the related entities without it and in other cases, it can't do anything with the related entities because I didn't include them:
Works without .Include();
This is an example where I'm loading data without .Include();
public class InvoiceService
{
private ApplicationDbContext db { get; set; }
public InvoiceService(ApplicationDbContext context)
{
db = context;
}
public Invoice Get(int id)
{
return db.Invoices.SingleOrDefault(x => x.Id == id);
}
}
public partial class ShowInvoice : System.Web.UI.Page
{
private InvoiceService invoiceService;
private readonly ApplicationDbContext context = new ApplicationDbContext();
protected void Page_Load(object sender, EventArgs e)
{
invoiceService = new InvoiceService(context);
if (!IsPostBack)
{
int.TryParse(Request.QueryString["invoiceId"].ToString(), out int invoiceId);
LoadInvoice(invoiceId);
}
}
private void LoadInvoice(int invoiceId)
{
var invoice = invoiceService.Get(invoiceId);
// Other code irrelevant to the question goes here.
}
}
Here follows the result which includes the data for the Company associated with the invoice I'm requested:
As you can see, the information for the company definitely comes through but was not explicitly included.
Doesn't work without .Include();
Conversely, I've done some mapping to do with invoices in this same project and I got NullReferenceExceptions when fetching the related entities property values because I didn't .Include().
This method gets all the approved timesheet entries for the specified company. This viewmodel is exclusively to be used when manipulating the association of timesheet entries for an invoice (so you're invoicing based on the timesheet entries selected).
public List<InvoiceTimesheetViewModel> GetInvoiceTimesheetsByCompanyId(int companyId)
{
var factory = new TimesheetViewModelsFactory();
var timesheets = db.Timesheets.Where(x => x.Approved && x.Company.Id == companyId && !x.Deleted).ToList();
return factory.GetInvoiceTimesheetsViewModel(timesheets);
}
NullReferenceExceptions occurred in the factory that maps the timesheet entities to the viewmodel:
public List<InvoiceTimesheetViewModel> GetInvoiceTimesheetsViewModel(List<Timesheet> timesheets)
{
var model = new List<InvoiceTimesheetViewModel>();
foreach (var timesheet in timesheets)
{
var start = DateTime.Parse((timesheet.DateAdded + timesheet.StartTime).ToString());
var finished = DateTime.Parse((timesheet.DateCompleted + timesheet.EndTime).ToString());
DateTime.TryParse(timesheet.RelevantDate.ToString(), out DateTime relevant);
model.Add(new InvoiceTimesheetViewModel
{
RelevantDate = relevant,
BillableHours = timesheet.BillableHours,
Finished = finished,
Id = timesheet.Id,
StaffMember = timesheet.StaffMember.UserName, // NRE here.
Start = start,
Task = timesheet.Task.Name // NRE here.
});
}
return model;
}
To fix these, I had to change the query that fetches the data to the following:
var timesheets = db.Timesheets.Include(i => i.StaffMember).Include(i => i.Task)
.Where(x => x.Approved && x.Company.Id == companyId && !x.Deleted).ToList();
Why is Entity Framework sometimes happy to give me data without me explicitly requesting that data and sometimes it requires me to explicitly request the data or else throws an error?
And how am I to know when I need to explicitly include the data I'm looking for and when I don't?
Entity framework uses lazy loading to load child relationships. For lazy loading to work property in the model should be marked with virtual keyword. Ef overrides it and adds lazy loading support.
When you have no virtual property EF has no way to load your child relationship data later, so the only time it's possible to do - during initial data loading using Include.
public class Timesheet
{
...
public virtual StaffMember StaffMember { get; set; }
public virtual Task Task { get; set; }
...
}
It depends on your models. If you have marked relational properties as virtual then you'll need to use .Include so EF knows that you need it. It is Lazy Loading. Preserves machine's memory and DB requests.
I am getting the error
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker
when trying to create a new entity and save it to the DB.
I understand the error and how it normally occurs, but in this instance all I am doing is creating a new entity and adding a few ints to it before saving, not adding any other entities from other contexts.
I have included the function that is causing the error. As you can see it is being passed an EndProduct which is an entity which is being tracked by a different context to the one in the _billableRepository, but since I am not trying to in anyway assign that entity to the newly created billable I don't see how it can be a problem.
The only way I can see the error happening is because a couple of the int values that are being assigned to the new Billable are taken from the existing EndProduct that is being tracked by a different context, but surely the IEntityChangeTracker doesn't track the individual primitives of an entity?
public void AddBillable(EndProduct endProduct, int? purchaseId, string centreCode, int userId)
{
if (endProduct.Product != null)
{
var existingBillableForUserForProductId = _billableRepository.GetQuery(b => b.UserId == userId && b.ProductId == endProduct.ProductId);
if (endProduct.BillablePartId != null && !existingBillableForUserForProductId.Any())
{
var billable = new Billable {
ProductId = endProduct.ProductId.Value, //int
UserId = userId, //int
PartId = endProduct.BillablePartId.Value, //int
DateAdded = DateTime.UtcNow, //datetime
PurchaseId = purchaseId, //int
CentreCode = centreCode //string
};
_billableRepository.Add(billable); //error here
_billableRepository.UnitOfWork.SaveChanges();
}
}
}
The most likely cause of this is to do with any dependency injection tool you're using.
There should only be one DbContext in play per unit of work. If you are newing up a new one each time, make sure the old one is disposed of.
Otherwise, you will have multiple instances of the same context running alongside each other.
This is where the change tracker gets confused and is unable to track changes to your entities.
On your model (GetById method) try to put something like this:
var billable = _db.Billable.AsNoTracking().SingleOrDefault(i => i.BillableId == id);
Use AsNoTracking() so that it returns a new query where the entities will not be cached in the System.Data.Entity.DbContext
You can fix by attaching your entity in the repository. Like this:
_context.Set<T>.Attach(entity)
Where Set its your DbSet in the context.
This sample code will solve the problem;
public class DataModel
{
private static DBContext context;
public static DBContext Context
{
get
{
if (context == null)
{
context = new SozlukContext();
return context;
}
return context;
}
}
}
public class EntryRepository : IEntryRepository
{
DBContext _context = DataModel.Context;
public IEnumerable<Data.Model.Entry> GetAll()
{
return _context.Entry.Select(x => x);
}
}
I have this piece of code
public int Update(Item item)
{
using (var ctx = new DataConext())
{
ctx.Entry(item).State = EntityState.Modified;
return ctx.SaveChanges();
}
}
Class Item
{
public string Name {get;set;}
public ICollection<Foobar> Foos {get;set;}
}
Class Foobar
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
Lets say:
item.Foos.ElementAt(0).FirstName = "edited name"
SaveChanged() is executed but I have the 'old' values on the database and not 'edited name'...
I can see the correct changes in Local in debug.
Looks like your object came from a different context that the one you are using now. In that case you can't do that with a generic because you need to do a foreEach in your Foobar collection and change the state for each item individually.
What you have here is a disconnected entity graph so the entity is disconnected and change tracking is lost. You only set the state of the main entity and so EF assumes that everything else is unchanged.
Jullie Lerman's books is a good source to understand how this works
What I would do is I would keep this method for simple entities but make it virtual so you can inherit this repo to create specific entity repos and override the update method with a more specific implementation suitable to an entity like the one in your example.
An article that helped my to design such a repo was this: http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application.
You are loading the object in a context and saving in another. Remove using (var ctx = new DataConext()) block, and search for a way to reach the context that loaded the item, then call SaveChanges(); Another way is pass the context to the method, like this public int Update(Item item, DbContext context) and save the changes.
Class Item
{
public string Name {get;set;}
public ICollection<Foobar> Foos {get;set;}
}
You need Include to include the Foos to the object manager. Right now, it is eager loading. Wherever you are loading the item, you have to include it.
You should use include, or you can use virtual to have them lazy load.