When do you need to .Include related entities in Entity Framework? - c#

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.

Related

Can't access entity framework relations outside Dbcontext when Lazyloading

I have a asp.net application using entity framework.
I have these two models:
public class CustomerModel
{
public int Id{get;set; }
[Required]
public string Name {get;set; }
[Required]
public string Surname { get; set; }
[Required]
[Range(18,110)]
public uint Age { get; set; }
[Required]
public virtual AdressModel Adress { get; set; }
[Required]
public DateTime Created { get; set; }
}
and
public class AdressModel
{
public int Id { get; set; }
[Required]
public int HouseNumber { get; set; }
[Required]
public string Town { get; set; }
[Required]
public string ZipCode { get; set; }
[Required]
public string Country { get; set; }
}
And a dbcontext class that looks like this:
public class DemoContext : DbContext
{
public DbSet<CustomerModel> Customers { get; set; }
public DbSet<AdressModel> Adresses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseLazyLoadingProxies();
options.UseSqlite(#"Data Source=/home/ask/RiderProjects/Parkz/identifier.sqlite");
}
}
and then I have a controller that just needs to load all the customers that I have in my database, and their adresses.
For that purpose I have this:
public IActionResult sendhere()
{
List<CustomerModel> customers = new List<CustomerModel>();
using (var db = new DemoContext()) {
customers = db.Customers
.Include(c => c.Adress)
.ToList();
}
return Content("hi");
}
Which I have tried to debug a bit.
The issue is that as soon as I exit my "using" block, all the related adress objects only consist of this error:
System.InvalidOperationException: An error was generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.LazyLoadOnDisposedContextWarning': An attempt was made to lazy-load navigation 'CustomerModelProxy.Adress' after the associated DbContext was disposed. This exception can be suppressed or logged by passing event ID 'CoreEventId.LazyLoadOnDisposedContextWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
at Microsoft.EntityFrameworkCore.Diagnostics.EventDefinition`2.Log[TLoggerCategory](IDiagnosticsLogger`1 logger, TParam1 arg1, TParam2 arg2)
at Microsoft.EntityFrameworkCore.Diagnostics.CoreLoggerExtensions.LazyLoadOnDisposedContextWarning(IDiagnosticsLogger`1 diagnostics, DbContext context, Object entityType, String navigationName)
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.LazyLoader.ShouldLoad(Object entity, String navigationName, NavigationEntry& navigationEntry)
at Microsoft.EntityFrameworkCore.Infrastruc…
Which is because I exited the context.
How do I still get to access the adresses of my customers, even though I exited the Dbcontext, so I can return them to a view
My general advice when working with EF is that entities shouldn't be referenced outside of the scope of their DbContext. You certainly can work with detached entities, but you have to respect that they are provided in an "as-is" state from the moment they leave the scope of the DbContext. For that reason I recommend that anything passed outside of the scope of the DbContext should be a POCO ViewModel or DTO class to avoid confusing whether an entity class is actually a functional, attached entity representing data domain state, or a detached shell.
Option 1: Deal with DTO/ViewModels.
public IActionResult sendhere()
{
using (var db = new DemoContext()) {
var customerDTOs = db.Customers
.Select(c => new CustomerDTO
{
// fill in the fields you want here.
Addresses = c.Addresses.Select(a => new AddressDTO
{
// fill in the address fields you want here.
}).ToList()
}).ToList();
return View(customerDTOs);
}
}
You can alternatively leverage Automapper, set up the desired projection rules and use ProjectTo<CustomerDTO>(config) to replace the use of Select() above.
When leveraging projection, you do not need lazy loading proxies at all. This has arguably become the defacto recommended approach for EF.
The advantages of the projection method are that these DTOs (or ViewModels) cannot be confused with being entities. The issue with detached entities is that where you have methods in your code that might accept entities, these methods might expect to get entities and access members that aren't loaded. If they are attached and within the scope of a DbContext, those members can be lazy-loaded (not ideal for performance reasons, but functional) however if they are detached you get errors or NullRefExceptions. The other advantage of projection is the payload of data being pulled from the database and sent to the view logic or end consists of just the data needed.
Option 2: Don't de-scope the DbContext. With projects like ASP.Net MVC web applications, you can leverage an IoC Container to provide dependency injection into your Controllers. In this way you can set up the DbContext to be injected into the constructor with a lifetime scope set to the Request. In this way, for any given request, all services/classes you might call can be managed by the container and have access to the DbContext.
public class SomeController
{
private readonly DemoContext _context;
public SomeController(DemoContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public IActionResult sendhere()
{
var customers = _context.Customers
.Include(c => c.Address)
.ToList();
return View(customers);
}
}
This can be combined with Option 1 to avoid needing to scope a DbContext with each request/action and better facilitate situations where you may want to make multiple calls against the DbContext and ensure the same context instance is used. For IoC containers there are a number of different ones available, and I believe ASP.Net Core comes with a default one, though I personally use and recommend Autofac. It has good documentation and examples on how to wire it up with MVC projects.
Option 3: Eager load everything you're going to need to reference. The example you provided should actually work, but your real code is likely missing an eager-load (.Include()) for the desired relationship given your example doesn't attempt to do anything with the Customers collection you load.
If your code does:
List<CustomerModel> customers = new List<CustomerModel>();
using (var db = new DemoContext()) {
customers = db.Customers
.Include(c => c.Address)
.ToList();
}
var firstAddressId = customers.FirstOrDefault()?.Address.Id;
This should work as Addresses was eager loaded. However, if you had:
List<CustomerModel> customers = new List<CustomerModel>();
using (var db = new DemoContext()) {
customers = db.Customers
.ToList();
}
var firstAddressId = customers.FirstOrDefault()?.Address.Id;
... without the Include(c => c.Address), then it would fail with that error.
With EF Core if you are going to want to return entities outside of the scope of a DbContext and you have lazy loading proxies enabled, you will want to temporarily turn off the proxy creation to avoid proxy errors. In this case anything you don't eager load will be left #null or default.
List<CustomerModel> customers = new List<CustomerModel>();
using (var db = new DemoContext()) {
db.ContextOptions.ProxyCreationEnabled = false;
customers = db.Customers
.Include(c => c.Address)
.ToList();
}
Return View(customers);
This should ensure that EF doesn't use proxies for the queries in the scope of that DbContext instance which should be avoided whenever you want to pass entities outside of the scope of the DbContext. This can be useful when you know you won't need the overhead of eager loading every reference. However, it is much better to use projection (Option 1) in this case to avoid future confusion around whether entities might actually have #null data, or merely it wasn't eager loaded.

Entity Framework: object returns with an empty list at first, but then suddenly the list is populated correctly

I have the following class:
public class User
{
public int Id { get; set; }
public List<User> Connections { get; set; }
//other properties
public User()
{
Connections = new List<User>();
}
}
Then I have a DataContext class for storage:
public class DataContext : DbContext
{
public DataContext() { }
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public virtual DbSet<User> Users { get; set; }
}
And a UserService class:
public class UserService: IUserService
{
private DataContext _context;
public UserService(DataContext context)
{
_context = context;
}
public User GetById(int id)
{
return _context.Users.Find(id);
}
...
}
Now suppose I correctly stored 2 users, and I add each other to their respective connection lists.
The problem is in the following piece of code:
var user1 = _userService.GetById(userId);
---> Here user1.Connections is an empty list (unexpected)
var results = anotherList.Select(x=>
{
---> Here user1.Connections have one object inside (the other user as expected)
});
I thought it was because the List was not populated yet since it was never accessed yet, but I also have a problem with the following endpoint in a controller:
var userId = int.Parse(User.Identity.Name);
var user1 = _userService.GetById(userId);
var connectionsInfo = user1.Connections.Select(x => new
{
Id = x.Id,
//map other properties
});
return Ok(connectionsInfo);
//this time an empty list is returned in the response, instead of a list with a single object
I read it might be regarding circular dependency, but I don't get any exception.
Also, I do not understand why in one case the list is populated after and in the other case is not populated at all.
Any idea what could be causing this?
Also I do not understand why in one case the list is populated after and in the other case is not populated at all.
It's the Lazy Loading feature in the entity framework. Lazy loading means delaying the loading of related data until you specifically request for it. For more explanation and a deep dive, you can review this good article.
Entity Framework supports three ways to load related data - eager loading, lazy loading, and explicit loading. for your scenario, It would prefer to use eager loading way. for achieving this goal EF has the Include() method. so, you can update your GetById method as below:
public User GetById(int id)
{
return _context.Users
.Include(item => item.Connections)
.Find(id);
}
With the above query when you find a specific user, its connections loads at the same time too. good luck.

Entity Framework Core Find and Update single value [duplicate]

What is the best approach to update database table data in Entity Framework Core?
Retrieve the table row, do the changes and save
Use keyword Update in DB context and handle exception for item not exist
What are the improved features we can use over EF6?
To update an entity with Entity Framework Core, this is the logical process:
Create instance for DbContext class
Retrieve entity by key
Make changes on entity's properties
Save changes
Update() method in DbContext:
Begins tracking the given entity in the Modified state such that it will be updated in the database when SaveChanges() is called.
Update method doesn't save changes in database; instead, it sets states for entries in DbContext instance.
So, We can invoke Update() method before to save changes in database.
I'll assume some object definitions to answer your question:
Database name is Store
Table name is Product
Product class definition:
public class Product
{
public int? ProductID { get; set; }
public string ProductName { get; set; }
public string Description { get; set; }
public decimal? UnitPrice { get; set; }
}
DbContext class definition:
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Your Connection String");
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>(entity =>
{
// Set key for entity
entity.HasKey(p => p.ProductID);
});
base.OnModelCreating(modelBuilder);
}
}
Logic to update entity:
using (var context = new StoreDbContext())
{
// Retrieve entity by id
// Answer for question #1
var entity = context.Products.FirstOrDefault(item => item.ProductID == id);
// Validate entity is not null
if (entity != null)
{
// Answer for question #2
// Make changes on entity
entity.UnitPrice = 49.99m;
entity.Description = "Collector's edition";
/* If the entry is being tracked, then invoking update API is not needed.
The API only needs to be invoked if the entry was not tracked.
https://www.learnentityframeworkcore.com/dbcontext/modifying-data */
// context.Products.Update(entity);
// Save changes in database
context.SaveChanges();
}
}
According to Microsoft docs:
the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflict
However, you should know that using Update method on DbContext will mark all the fields as modified and will include all of them in the query. If you want to update a subset of fields you should use the Attach method and then mark the desired field as modified manually.
context.Attach(person);
context.Entry(person).Property(p => p.Name).IsModified = true;
context.SaveChanges();
public async Task<bool> Update(MyObject item)
{
Context.Entry(await Context.MyDbSet.FirstOrDefaultAsync(x => x.Id == item.Id)).CurrentValues.SetValues(item);
return (await Context.SaveChangesAsync()) > 0;
}
It's super simple
using (var dbContext = new DbContextBuilder().BuildDbContext())
{
dbContext.Update(entity);
await dbContext.SaveChangesAsync();
}
Microsoft Docs gives us two approaches.
Recommended HttpPost Edit code: Read and update
This is the same old way we used to do in previous versions of Entity Framework. and this is what Microsoft recommends for us.
Advantages
Prevents overposting
EFs automatic change tracking sets the Modified flag on the fields that are changed by form input.
Alternative HttpPost Edit code: Create and attach
an alternative is to attach an entity created by the model binder to the EF context and mark it as modified.
As mentioned in the other answer the read-first approach requires an extra database read, and can result in more complex code for handling concurrency conflicts.
Assume we have an entity Student and AppDbContext as follows.
class Student
{
public int Id { get; set; }
public string Name { get; set; } = default!;
public int Age { get; set; }
}
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> opts) : base(opts) { }
public DbSet<Student> Students { get; set; }
}
Version A
CurrentValues can only work for a tracked entity (found).
Only the changed properties are marked as Modified.
Automatic property mapping that is useful when using type parameter TEntity instead of a fixed type Student.
async Task Edit_A(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Entry(found).CurrentValues.SetValues(incoming);
await db.SaveChangesAsync();
}
}
Version B
It works only on a tracked entity (found).
It is not necessary to map all properties because only the changed properties are marked as Modified.
Manual property mapping so we cannot not use generic type parameter.
async Task Edit_B(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
found.Name = incoming.Name;
found.Age = incoming.Age;
await db.SaveChangesAsync();
}
}
Version C
Update() works only on an untracked entity (incoming) and makes it tracked. Untracking found before invoking Update(incoming) is mandatory because only one entity can be tracked with the given primary key.
All properties (including unchanged ones) are marked as Modified. It is less efficient.
Automatic property mapping that is useful for generic type parameter.
async Task Edit_C(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Students.Entry(found).State = EntityState.Detached;
db.Students.Update(incoming);
await db.SaveChangesAsync();
}
}
Version D
It is the same as version C. I rewrite again below for the sake of completeness.
It works only on an untracked entity (incoming) and makes it tracked. Untracking found is mandatory because only one entity can be tracked with the given primary key.
All properties (including unchanged ones) are marked as Modified. It is less efficient.
Automatic property mapping that is useful for generic type parameter.
async Task Edit_D(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Students.Entry(found).State = EntityState.Detached;
db.Students.Entry(incoming).State = EntityState.Modified;
await db.SaveChangesAsync();
}
}
Version E
It works only on an untracked entity (incoming) and makes it tracked. Untracking found is mandatory because only one entity can be tracked with the given primary key.
It is not necessary to map all properties because only properties (including unchanged ones) marked with IsModified=true will be updated. It is less efficient if you mark IsModified=true for unchanged properties.
Manual property mapping so we cannot not use generic type parameter.
async Task Edit_E(int id, Student incoming, AppDbContext db)
{
if (await db.Students.FindAsync(id) is Student found)
{
db.Students.Entry(found).State = EntityState.Detached;
db.Students.Entry(incoming).Property(s => s.Name).IsModified = true;
db.Students.Entry(incoming).Property(s => s.Age).IsModified = true;
await db.SaveChangesAsync();
}
}
I set it as a Community Wiki, feel free to edit as many as you want.
After going through all the answers I thought i will add two simple options
If you already accessed the record using FirstOrDefault() with tracking enabled (without using .AsNoTracking() function as it will disable tracking) and updated some fields then you can simply call context.SaveChanges()
In other case either you have entity posted to server using HtppPost or you disabled tracking for some reason then you should call context.Update(entityName) before context.SaveChanges()
1st option will only update the fields you changed but 2nd option will update all the fields in the database even though none of the field values were actually updated :)
A more generic approach
To simplify this approach an "id" interface is used
public interface IGuidKey
{
Guid Id { get; set; }
}
The helper method
public static void Modify<T>(this DbSet<T> set, Guid id, Action<T> func)
where T : class, IGuidKey, new()
{
var target = new T
{
Id = id
};
var entry = set.Attach(target);
func(target);
foreach (var property in entry.Properties)
{
var original = property.OriginalValue;
var current = property.CurrentValue;
if (ReferenceEquals(original, current))
{
continue;
}
if (original == null)
{
property.IsModified = true;
continue;
}
var propertyIsModified = !original.Equals(current);
property.IsModified = propertyIsModified;
}
}
Usage
dbContext.Operations.Modify(id, x => { x.Title = "aaa"; });
Personally I see the operation that you are doing is an upsert operation, where if data already exist we update, else we insert. There is one good library from Flexlab to support Upsert operation with this syntax
var country = new Country
{
Name = "Australia",
ISO = "AU",
Created = DateTime.UtcNow,
};
await DataContext.Upsert(country)
.On(c => c.ISO)
.UpdateColumns(c => new Country
{
Name = "Australia"
Updated = DateTime.UtcNow,
})
.RunAsync();
The code will check for the property Country.ISO in the table. If it does not exist yet it will insert new row altogether into the table. Else if any row with the same Country.ISO already exist it will update columns Country.Name and Country.Updated for that row.
This method is very fast because we only do one call to the database instead of two calls to check if the data already exist before we updating or inserting the data.
Note that this answer does not apply to you if your intend is not to do the Upsert operation

Concisely Insert/Update/Delete Enumerable Child Entities using Automapper

I am working on an ASP.Net MVC application and I have a "Report" object that has related enumerables such as schedules and comments. Using AutoMapper, it has been easy to convert from a report entity to a View Model and back, but I have issues when I try to save the Report object (mapped to an existing entity from a view model) back to the database.
More specifically, I can't seem to concisely update existing entities, insert new entities, and delete old entities using automapper. For instance whenever I map schedules from the view model to a report entity, it deletes the existing schedules and then creates new ones (with incremented indexes). This is my code:
public class ScheduleViewModel
{
public int ScheduleID { get; set; }
public int ReportID { get; set; }
public int Month { get; set; }
public int Day { get; set; }
public string Description { get; set; }
}
public class ReportViewModel
{
public int ReportID { get; set; }
public List<ScheduleViewModel> Schedules { get; set; }
public void Save()
{
dbContext db = new dbContext();
Report original = db.Reports.SingleOrDefault(o => o.ReportID == ReportID);
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ReportViewModel, Report>();
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
mapper.Map(this, original);
db.SaveChanges();
}
}
My Report object has a relational key (and a "Schedules" navigational property), so everything is mapped successfully from my view model to the "original" Report. New schedules have a ScheduleID of 0, since they haven't been assigned, and they get added to the database using the auto-increment, which is what I want. The existing schedules maintain their ScheduleID when mapped to the "original" report object, but then recieve incremented IDs once SaveChanges is called.
As I understand it, I'm attaching new schedules to the context whether or not the view model ID properties match the primary key in the database (in this case it is a composite of ReportID and ScheduleID). Is there a clean way, using some sort of ForMember(report => report.Schedules), expression that makes Entity Framework understand to not destroy my existing entities if a View Model object can map to an existing Key?
I am looking for something that functions similar to the code below, but since I will have many enumerable properties attached to my report objects, I don't want to maintain these sections for each:
foreach (Schedule schedule in db.Schedules.Where(s => s.ReportID == this.ReportID))
{
ScheduleViewModel svm = this.Schedules.FirstOrDefault(s => s.ScheduleID == schedule.ScheduleID);
//Update Existing
if (svm != null)
db.Entry(schedule).CurrentValues.SetValues(svm);
//Delete Missing
else
db.Entry(schedule).State = System.Data.Entity.EntityState.Deleted;
}
//Insert New
foreach(ScheduleViewModel svm in this.Schedules.Where(s => s.ScheduleID == 0))
{
svm.ReportID = ReportID;
Schedule schedule = new Schedule() {};
db.Schedules.Add(schedule);
db.Entry(schedule).CurrentValues.SetValues(svm);
}
Apparently this is not currently possible with AutoMapper. Instead of mapping individual entities, AutoMapper destroys the existing entity collection and creates a new one with the same properties. I'm sure that works fine for some applications, but with Entity Framework's change tracking it is telling the database to delete the existing records and insert new ones, with new IDs. Unfortunately it seems collections have to be mapped individually/manually using the method I posted in my original question. Rather than repeating that for every collection, though, I wrote a generic handler that will map a model collection to an entity collection using a specified key -- without destroying the entities:
public void UpdateEntitySet<T>(IEnumerable<object> models, IEnumerable<T> entities, string key) where T : class
{
Dictionary<object, T> entityDictionary = new Dictionary<object, T>();
foreach(T entity in entities)
{
var entityKey = entity.GetType().GetProperty(key).GetValue(entity);
entityDictionary.Add(entityKey, entity);
}
if (models != null)
{
foreach (object model in models)
{
var modelKey = model.GetType().GetProperty(key).GetValue(model);
var existingEntity = entityDictionary.SingleOrDefault(d => Object.Equals(d.Key, modelKey)).Value;
if (existingEntity == null)
{
var newEntity = db.Set<T>().Create();
db.Set<T>().Add(newEntity);
db.Entry(newEntity).CurrentValues.SetValues(model);
}
else
{
db.Entry(existingEntity).CurrentValues.SetValues(model);
entityDictionary.Remove(entityDictionary.Single(d => Object.Equals(d.Key, modelKey)).Key);
}
}
}
for (int i = 0; i < entityDictionary.Count; i++)
db.Entry(entityDictionary.ElementAt(i).Value).State = System.Data.Entity.EntityState.Deleted;
}
If you have a composite key, you'll have to modify the code a little, otherwise you can ignore the mapping with AutoMapper and then use the method above like so:
IEnumerable<Schedule> ScheduleEntities = db.Set<Schedule>().Where(s => s.ReportID == ReportID);
UpdateEntitySet<Schedule>(ScheduleViewModels, ScheduleEntities, "ScheduleID");

Updating many-to-many relationships with a generic repository

I have a database context with lazy loading disabled. I am using eager loading to load all of my entities. I cannot update many to many relationships.
Here's the repository.
public class GenericRepository<TEntity> : IGenericRepository<TEntity>
where TEntity : class
{
... other code here...
public virtual void Update(TEntity t)
{
Set.Attach(t);
Context.Entry(t).State = EntityState.Modified;
}
...other code here...
}
Here's the User model.
public partial class User
{
public User()
{
this.Locks = new HashSet<Lock>();
this.BusinessModels = new HashSet<BusinessModel>();
}
public int UserId { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string JobTitle { get; set; }
public string RecoveryEmail { get; set; }
public Nullable<double> Zoom { get; set; }
public virtual ICollection<Lock> Locks { get; set; }
public virtual ICollection<BusinessModel> BusinessModels { get; set; }
}
If I modify the business models collection, it does not save the business models collection although I have attached the entire entity.
Worker.UserRepository.Update(user);
I'm not sure what is going on. I don't want to break my generic repository/unit of work pattern just to update many-to-many relationships.
Edit 2: I've got this working...but it is extremely different from the pattern that I'm going for. Having hard implementations means I will need to create a method for each type that has a many to many relationship. I am investigating now to see if I can make this a generic method.
Edit 3: So the previous implementation I had did not work like I thought it would. But now, I have a slightly working implementation. If someone would please help me so I can move on from this, I will love you forever.
public virtual void Update(TEntity updated,
IEnumerable<object> set,
string navigationProperty,
Expression<Func<TEntity, bool>> filter,
Type propertyType)
{
// Find the existing item
var existing = Context.Set<TEntity>().Include(navigationProperty).FirstOrDefault(filter);
// Iterate through every item in the many-to-many relationship
foreach (var o in set)
{
// Attach it if its unattached
if (Context.Entry(o).State == EntityState.Detached)
// Exception "an object with the same key already exists"
// This is due to the include statement up above. That statement
// is necessary in order to edit the entity's navigation
// property.
Context.Set(propertyType).Attach(o);
}
// Set the new value on the navigation property.
Context.Entry(existing).Collection(navigationProperty).CurrentValue = set;
// Set new primitive property values.
Context.Entry(existing).CurrentValues.SetValues(updated);
Context.Entry(existing).State = EntityState.Modified;
}
I then call it like this:
Worker.UserRepository.Update(user, user.BusinessModels, "BusinessModels", i => i.UserId == user.UserId, typeof (BusinessModel));
Extremely messy, but it lets me update many-to-many relationships with generics. My big problem is the exception when I go to attach new values that already exist. They're already loaded because of the include statement.
This works:
This doesn't:
After many painful hours, I have finally found a way to update many-to-many relationships with a completely generic repository. This will allow me to create (and save) many different types of entities without creating boilerplate code for each one.
This method assumes that:
Your entity already exists
Your many to many relationship is stored in a table with a composite key
You are using eager loading to load your relationships into context
You are using a unit-of-work/generic repository pattern to save your entities.
Here's the Update generic method.
public virtual void Update(Expression<Func<TEntity, bool>> filter,
IEnumerable<object> updatedSet, // Updated many-to-many relationships
IEnumerable<object> availableSet, // Lookup collection
string propertyName) // The name of the navigation property
{
// Get the generic type of the set
var type = updatedSet.GetType().GetGenericArguments()[0];
// Get the previous entity from the database based on repository type
var previous = Context
.Set<TEntity>()
.Include(propertyName)
.FirstOrDefault(filter);
/* Create a container that will hold the values of
* the generic many-to-many relationships we are updating.
*/
var values = CreateList(type);
/* For each object in the updated set find the existing
* entity in the database. This is to avoid Entity Framework
* from creating new objects or throwing an
* error because the object is already attached.
*/
foreach (var entry in updatedSet
.Select(obj => (int)obj
.GetType()
.GetProperty("Id")
.GetValue(obj, null))
.Select(value => Context.Set(type).Find(value)))
{
values.Add(entry);
}
/* Get the collection where the previous many to many relationships
* are stored and assign the new ones.
*/
Context.Entry(previous).Collection(propertyName).CurrentValue = values;
}
Here's a helper method I found online which allows me to create generic lists based on whatever type I give it.
public IList CreateList(Type type)
{
var genericList = typeof(List<>).MakeGenericType(type);
return (IList)Activator.CreateInstance(genericList);
}
And from now on, this is what calls to update many-to-many relationships look like:
Worker.UserRepository.Update(u => u.UserId == user.UserId,
user.BusinessModels, // Many-to-many relationship to update
Worker.BusinessModelRepository.Get(), // Full set
"BusinessModels"); // Property name
Of course, in the end you will need to somewhere call:
Context.SaveChanges();
I hope this helps anyone who never truly found how to use many-to-many relationships with generic repositories and unit-of-work classes in Entity Framework.
#dimgl Your solution worked for me. What I've done in addition was to replace the hard-coded type and name of the primaryKey with dynamically retrieved ones:
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
ObjectSet<TEntity> set = objectContext.CreateObjectSet<TEntity>();
IEnumerable<string> keyNames = set.EntitySet.ElementType.KeyMembers.Select(k => k.Name);
var keyName = keyNames.FirstOrDefault();
var keyType = typeof(TEntity).GetProperty(keyName).PropertyType
foreach (var entry in updatedSet
.Select(obj =>
Convert.ChangeType(obj.GetType()
.GetProperty(keyName)
.GetValue(obj, null), keyType))
.Select(value => context.Set<TEntity>().Find(value)))
{
values.Add(entry);
}
Like this your code won't depend on the Entity key's name and type.

Categories

Resources