After spending a few days trying to set up a simple application with EF and DDD I have to say that I feel quite frustrated and think that I was better off using Linq-to-SQL and forget all about DDD and EF.
With EF
a) You cannot have proper readonly collections
b) When you remove something from a collection of child items you quite often get that The relationship could not be changed because one or more of the foreign-key properties is non-nullable message
c) There is no easy way of deleting all child items of a parent and reinserting them
All these are pretty much show stoppers for me given that the workarounds I have found are quite nasty looking. Has someone managed to put together a simple repository that addresses these issues?
If yes would you be kind enough to share some code?!?
Also, and I know this is a big topic, does anyone have any hands on experience of any real world DDD benefits in large scale web applications? We all know the theory but it would be nice to have an idea if it is actually worth the hassle!
Ok, the best i can do so far without having to do all sorts of carzy workarounds is to use
AsNoTracking() when i query something. That way i get my info and EF leaves alone without doing
whatever the hell it does behind my back. I can now Remove from a collection and i can kind of
be able to delete as well (who would think that i d have to go back to sql fro this!)
Does anyone know any pitfalls of using AsNoTracking? As far as i can genearate SQL based
on my objects and populate them or update/delete them i am fine. The whole tracking thing
goes too far anyway?
namespace EShop.Models.Repositories
{
public class CustomerRepository : BaseRepository, IRepository<Customer, Int32>
{
public CustomerRepository() : base(new EShopData()) { }
#region CoreMethods
public void InsertOrUpdate(Customer customer)
{
if (customer.CustomerId > 0)
{
// you cannot use remove, if you do you ll attach and then you ll have issues with the address/cards below
// dbContext.Entry<CustomerAddress>(address).State = EntityState.Added; will fail
dbContext.Database.ExecuteSqlCommand("DELETE FROM CustomerAddress WHERE CustomerId = #CustomerId", new SqlParameter("CustomerId", customer.CustomerId));
dbContext.Database.ExecuteSqlCommand("DELETE FROM CreditCard WHERE CustomerId = #CustomerId", new SqlParameter("CustomerId", customer.CustomerId));
foreach (var address in customer.Addresses)
dbContext.Entry<CustomerAddress>(address).State = EntityState.Added;
foreach (var card in customer.CreditCards)
dbContext.Entry<CreditCard>(card).State = EntityState.Added;
dbContext.Entry<Customer>(customer).State = EntityState.Modified;
}
else
{
dbContext.Entry<Customer>(customer).State = EntityState.Added;
foreach (var card in customer.CreditCards)
dbContext.Entry<CreditCard>(card).State = EntityState.Added;
foreach (var address in customer.Addresses)
dbContext.Entry<CustomerAddress>(address).State = EntityState.Added;
}
}
public void Delete(int customerId)
{
var existingCustomer = dbContext.Customers.Find(customerId);
if (existingCustomer != null)
{
//delete cards
var creditCards = dbContext.CreditCards.Where(c => c.CustomerId == customerId);
foreach (var card in creditCards)
dbContext.Entry<CreditCard>(card).State = EntityState.Deleted;
//delete addresses
var addresses = dbContext.CustomerAddresses.Where(c => c.CustomerId == customerId);
foreach (var address in addresses)
dbContext.Entry<CustomerAddress>(address).State = EntityState.Deleted;
//delete basket
dbContext.Entry<Customer>(existingCustomer).State = EntityState.Deleted;
}
}
public Customer GetById(int customerId)
{
return dbContext.Customers.Include("Addresses").AsNoTracking().SingleOrDefault(c => c.CustomerId == customerId);
}
public IList<Customer> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
{
return null;
}
public void Save()
{
dbContext.SaveChanges();
}
#endregion CoreMethods
#region AdditionalMethods
#endregion AdditionalMethods
}
}
Response to b: When you create your database, you must either cascade deletes (that is the database removes all related child records, too) or have the foreign key nullable. Then you won't get that error. This isn't to blame on EF, it's the way how an relational database handles constraints. You can configure this in your EDMX, your code first or using DDL on the database side. Depending on your decision how you did set up your project.
Response to c: more a general feeling, but deleting all children and reinserting sounds quite error prone and has a 'smell'. At least I would do that only if it is absolutely required. From a performance point of view, updating is probably faster. Maybe you can rethink the problem why you chose to delete and reinsert?
ok i think that i ve had enough of this for now so i ll summarize my rather negative experience
a) It is kind of possible but since this is version 5 i expected something better.
probably the easiest and simpler workaround can be found here
http://edo-van-asseldonk.blogspot.co.uk/2012/03/readonly-collections-with-entity.html
or i suppose you can even come up with your own readonly collection specific to the issue at hand
such as BasketProductsReadOnlyCollection if you have a basket and a collection of its products.
b) Probably we do not have to worry about a anyway. In a "stroke of genius" microsoft made it pretty
much impossible to write proper DDD code given the problem here. If you have a Basket and Products
with a BasketId in your Products table that is not nullable then you are in trouble if you do
Basket.RemoveProduct(product). Removing something like this means that the "relationship" is removed not the record. So EF will try to set BasketId to null and if it cant it ll throw an exception (and
no i dont want to make it nullable just to suit EF, even if i wanted what if i work with a DBA who doesnt?) what you need to do is call dbContext.Products.Remove(product) to make sure that it is deleted. That basically means that your business logic code needs to be aware of dbContext
c) I cant be bothered any more! Again there are responses about this on StackOverflow and you can possibly get something up and running but it should not be that difficult and counter intuitive.
As for the bigger picture, i had a look at the N-Tier recommendations that work with "Detached "Entities. I read a book from Julia Lerman who seems to be the authority on the subject and i m not impressed. The way the whole attaching an object graph works and the recommended ways of handling this are again very counter intuitive. Her recommended approach to make things "simple" was to have each object record its state in your business code! not my cup of tea.
I dont consider myself an architectural genius or something and perhaps i m missing something (or a lot) but to me EF's efforts seem to be misplaced. They spent so much time and money implementing this
whole tracking system that is supposed to do everything for you (typical MS, they think we are too stupid or something to look after our own stuff) instead of focusing on other things that could make this prouduct a lot easier to use.
What i want from my ORM is to deliver the data for me in my objects, then LEAVE ME ALONE to process
them in whatever way i want and then i want to pass my object or object graph back to the ORM and have the freedom to tell it what i want to add/delete/update from the object graph and how without the current shenanigans of EF.
Bottom line: i think i ll give MS a couple more years on this, they ll probably get it right in the end but this is not for me yet. And will MS finally put some proper documentation/tutorials on their sites? I remember reading a 300 hundred pages PDF tutorial on NHibernate years ago.
In case anyone else is struggling with this, this is the best implementation i could come up with, look at the RemoveFromBasket, AddToBasket methods, not ideal but at least you get something up & running
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Helpers;
using EShop.Models.DomainModel;
using System.Data;
using EShop.Models.DataAccess;
using System.Data.Objects;
using System.Data.Entity.Infrastructure;
namespace EShop.Models.Repositories
{
public class BasketRepository : BaseRepository, IRepository<Basket, Int32>
{
public BasketRepository() : base(new EShopData()) { }
#region CoreMethods
public void InsertOrUpdate(Basket basket)
{
var basketInDB = dbContext.Baskets.SingleOrDefault(b => b.BasketId == basket.BasketId);
if (basketInDB == null)
dbContext.Baskets.Add(basket);
}
public void Delete(int basketId)
{
var basket = this.GetById(basketId);
if (basket != null)
{
foreach (var product in basket.BasketProducts.ToList())
{
basket.BasketProducts.Remove(product); //delete relationship
dbContext.BasketProducts.Remove(product); //delete from DB
}
dbContext.Baskets.Remove(basket);
}
}
public Basket GetById(int basketId)
{
// eager-load product info
var basket = dbContext.Baskets.Include("BasketProducts")
.Include("BasketProducts.Product.Brand").SingleOrDefault(b => b.BasketId == basketId);
return basket;
}
public IList<Basket> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
{
throw new NotImplementedException();
}
public void Save()
{
dbContext.SaveChanges();
}
#endregion CoreMethods
#region AdditionalMethods
public void AddToBasket(Basket basket, Product product, int quantity)
{
var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId);
if (existingProductInBasket == null)
{
var basketProduct = new BasketProduct()
{
BasketId = basket.BasketId,
ProductId = product.ProductId,
Quantity = quantity
};
basket.BasketProducts.Add(basketProduct);
}
else
{
existingProductInBasket.Quantity = quantity;
}
}
public void RemoveFromBasket(Basket basket, Product product)
{
var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId);
if (existingProductInBasket != null)
{
basket.BasketProducts.Remove(existingProductInBasket); //delete relationship
dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB
}
}
public void RemoveFromBasket(BasketProduct basketProduct)
{
var basket = dbContext.Baskets.Find(basketProduct.BasketId);
var existingProductInBasket = dbContext.BasketProducts.Find(basketProduct.BasketId, basketProduct.ProductId);
if (basket != null && existingProductInBasket != null)
{
basket.BasketProducts.Remove(existingProductInBasket); //delete relationship
dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB
}
}
public void ClearBasket(Basket basket)
{
foreach (var product in basket.BasketProducts.ToList())
basket.BasketProducts.Remove(product);
}
#endregion AdditionalMethods
}
}
Ok, looks like i ve managed to get everything working with EF 5 more or less the way i want them.
Problem b seems to be ok with EF5. I think that i now have a proper DDD basket class and a proper repository so quite happy with that, perhaps i wan unfair being too harsh with EF after all!
public partial class Basket
{
public Basket()
{
this.BasketProducts = new List<BasketProduct>();
}
public int BasketId { get; set; }
public int? CustomerId { get; set; }
public decimal TotalValue { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
public ICollection<BasketProduct> BasketProducts { get; private set; }
public void AddToBasket(Product product, int quantity)
{
//BUSINESS LOGIC HERE
var productInBasket = BasketProducts.SingleOrDefault(b => b.BasketId == this.BasketId && b.ProductId == product.ProductId);
if (productInBasket == null)
{
BasketProducts.Add(new BasketProduct
{
BasketId = this.BasketId,
ProductId = product.ProductId,
Quantity = quantity
});
}
else
{
productInBasket.Quantity = quantity;
}
}
public void RemoveFromBasket(Product product)
{
//BUSINESS LOGIC HERE
var prodToRemove = BasketProducts.SingleOrDefault(b => b.BasketId == this.BasketId && b.ProductId == product.ProductId);
BasketProducts.Remove(prodToRemove);
}
}
}
public class BasketRepository : BaseRepository, IRepository<Basket, Int32>
{
public BasketRepository() : base(new EShopData()) { }
#region CoreMethods
//public void InsertOrUpdate(Basket basket, bool persistNow = true) { }
public void Save(Basket basket, bool persistNow = true)
{
var basketInDB = dbContext.Baskets.SingleOrDefault(b => b.BasketId == basket.BasketId);
if (basketInDB == null)
dbContext.Baskets.Add(basket);
if (persistNow)
dbContext.SaveChanges();
}
public void Delete(int basketId, bool persistNow = true)
{
var basket = this.GetById(basketId);
if (basket != null)
{
foreach (var product in basket.BasketProducts.ToList())
{
basket.BasketProducts.Remove(product); //delete relationship
dbContext.BasketProducts.Remove(product); //delete from DB
}
dbContext.Baskets.Remove(basket);
}
if (persistNow)
dbContext.SaveChanges();
}
public Basket GetById(int basketId)
{
// eager-load product info
var basket = dbContext.Baskets.Include("BasketProducts")
.Include("BasketProducts.Product.Brand").SingleOrDefault(b => b.BasketId == basketId);
return basket;
}
public IList<Basket> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
{
throw new NotImplementedException();
}
public void SaveForUnitOfWork()
{
dbContext.SaveChanges();
}
}
a) What are you trying to do in the first place? Can't you make the collection private and expose only public property that takes a snapshot of it?
b) To remove a child entity from the database, use dbcontext.ThatEntitySet.Remove(child), not parent.Children.Remove(child).
Or you can make an identifying relationship by making a foreign key in the child a part of the primary key. Then parent.Children.Remove(child) would remove a row from DB.
c) Seems that you're doing something stupid. If you provided details I would propose a different solution.
Big topic: Is your domain complex enough? Or you're just trying to apply... to force DDD patterns in a simple CRUD application? What business rules do you have? Invariants? What methods do your entities have? Are there any policies?
Why would you ever need an InsertOrUpdate method? I suppose that you invented it because you use just the same form for creating and updating an entity. That is a strong signal, that you're just doing a CRUD app.
Related
In this example a user has zero or many bills, one bill can be assigned to one user. Bill can also be created but never assigned.
public class User
{
public int Id{ get; set; }
public List<Bill> bills{ get; set; }
}
public class Bill
{
public int Id { get; set; }
public int userId{ get; set; }
public User user{ get; set; }
}
I've also added this in my DB context configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Bill>()
.HasOne(b => b.user)
.WithMany(u => u.bills)
.HasForeignKey(b => b.userId);
}
I've realized it through a unit of work + repository pattern. In my BillService.cs I would like to have a method that allows me to update/add a bill and assign it to a user.
If the user doesn't exist in DB it should add it. If the user exists it should update it.
I've tried two approaches.
First:
public async Task<void> AddUpdateBill(AddBillModel model){
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
Second:
public async Task<void> AddUpdateBill(AddBillModel model)
{
Bill bill= await unitOfWork.BillRepository.GetByID(model.billId);
bill.user = model.user;
unitOfWork.BillRepository.Update(bill);
unitOfWork.Save();
}
In both cases, I've got the problem of duplicated primary-key or entity already tracked.
Which is the best approach or the right way to do it?
EDIT: Sorry, BillRepo and BillRepository are the same class.
public async Task<Bill> GetByID(int id)
{
return await context
.bill
.Include(b => b.user)
.Where(b=> b.id == id)
.FirstOrDefaultAsync();
}
public void Update(Bill bill)
{
context.Entry(bill).CurrentValues.SetValues(bill);
}
The first approach seems more right (to me).
First of all, comply with the naming rules: all properties must begin with upper case characters. "Bills", "UserId", "User" in your case.
if( unitOfWork.UserRepo.GetById(model.userId) == null){
unitOfWork.UserRepo.Insert(model.user);
}else{
unitOfWork.UserRepo.Update(model.user);
}
bill.user = model.user;
You don't need it here
bill.user = model.user;
because you have just attached your entity to context and updated/inserted it.
Also, don't forget to format your code, for example https://learn.microsoft.com/ru-ru/dotnet/csharp/programming-guide/inside-a-program/coding-conventions
It would be useful to consider inserting/updating your entities not straight from the model, something like:
if( unitOfWork.UserRepo.GetById(model.userId) == null){
var user = new User
{
//set properties
};
unitOfWork.UserRepo.Insert(user);
unitOfWork.Save();
bill.userId = user.Id;
}
Here:
if( unitOfWork.UserRepo.GetById(model.userId) == null){...
you retrieve the User from UserRepo but don't assign it to any variable. This may cause the exception stating that there are multiple tracked entities with the same ID.
Try to retrieve (including bills) or create the User entity and add the new bill in there. Then insert User entity to DB (if it was not there) and simply Save your work.
I've been having some rough time sorting out how to create an Restful API with EntityFramework. The problem is mainly because this API should be used in a long time forward and i want to be it as maintainable and clean, with good performance. Enough of that, let's get into the problem.
Disclamer:
Because of companypolicy and can't post too much here, but i will try to address the problem the best way possible. There will also just be snippets of code, and may not be valid. I'm also fairly new to C# and as a JuniorD i have never touched an API before.. And excuse my english, this is my second language.
Every model derives from the BaseModel class
public class BaseModel
{
[Required]
public Guid CompanyId { get; set; }
public DateTime CreatedDateTime { get; set; }
[StringLength(100)]
public string CreatedBy { get; set; }
public DateTime ChangedDateTime { get; set; }
[StringLength(100)]
public string ChangedBy { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; }
}
public class Carrier : BaseModel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid CarrierId { get; set; }
public int CarrierNumber { get; set; }
[StringLength(100)]
public string CarrierName { get; set; }
[StringLength(100)]
public string AddressLine { get; set; }
public Guid? PostOfficeId { get; set; }
public PostOffice PostOffice { get; set; }
public Guid? CountryId { get; set; }
public Country Country { get; set; }
public List<CustomerCarrierLink> CustomerCarrierLinks { get; set; }
}
Every repository derives from Repository and has it's own interface.
public class CarrierRepository : Repository<Carrier>, ICarrierRepository
{
public CarrierRepository(CompanyMasterDataContext context, UnitOfWork unitOfWork) : base(context, unitOfWork) { }
#region Helpers
public override ObjectRequestResult<Carrier> Validate(Carrier carrier, List<string> errorMessages)
{
var errorMessages = new List<string>();
if(carrier != null)
{
var carrierIdentifier = (carrier.CarrierName ?? carrier.CarrierNumber.ToString()) ?? carrier.CarrierGLN;
if (string.IsNullOrWhiteSpace(carrier.CarrierName))
{
errorMessages.Add($"Carrier({carrierIdentifier}): Carrier name is null/empty");
}
}
else
{
errorMessages.Add("Carrier: Cannot validate null value.");
}
return CreateObjectResultFromList(errorMessages, carrier); // nonsense
}
}
UnitOfWork derives from class UnitOfWorkDiscoverySet, this class initializes the repository properties using reflection and also contains a method (OnBeforeChildEntityProcessed) for calling every OnBeforeChildEntityProcessed.
public class UnitOfWork : UnitOfWorkDiscoverySet
{
public UnitOfWork(CompanyMasterDataContext context)
: base(context){}
public CarrierRepository Carriers { get; internal set; }
public PostOfficeRepository PostOffices { get; internal set; }
public CustomerCarrierLinkRepository CustomerCarrierLinks { get; internal set; }
}
public IRepository<Entity> where Entity : BaseModel
{
ObjectRequestResult<Entity> Add(Entity entity);
ObjectRequestResult<Entity> Update(Entity entity);
ObjectRequestResult<Entity> Delete(Entity entity);
ObjectRequestResult<Entity> Validate(Entity entity);
Entity GetById(Guid id);
Guid GetEntityId(Entity entity);
}
public abstract class Repository<Entity> : IRepository<Entity> where Entity : BaseModel
{
protected CompanyMasterDataContext _context;
protected UnitOfWork _unitOfWork;
public Repository(CompanyMasterDataContext context, UnitOfWork unitOfWork)
{
_context = context;
_unitOfWork = unitOfWork;
}
public ObjectRequestResult<Entity> Add(Entity entity)
{
if (!EntityExist(GetEntityId(entity)))
{
try
{
var validationResult = Validate(entity);
if (validationResult.IsSucceeded)
{
_context.Add(entity);
_context.UpdateEntitiesByBaseModel(entity);
_context.SaveChanges();
return new ObjectRequestResult<Entity>()
{
ResultCode = ResultCode.Succceeded,
ResultObject = entity,
Message = OBJECT_ADDED
};
}
return validationResult;
}
catch (Exception exception)
{
return new ObjectRequestResult<Entity>()
{
ResultCode = ResultCode.Failed,
ResultObject = entity,
Message = OBJECT_NOT_ADDED,
ErrorMessages = new List<string>()
{
exception?.Message,
exception?.InnerException?.Message
}
};
}
}
return Update(entity);
}
public virtual ObjectRequestResult Validate(Entity entity)
{
if(entity != null)
{
if(!CompanyExist(entity.CompanyId))
{
return EntitySentNoCompanyIdNotValid(entity); // nonsense
}
}
return EntitySentWasNullBadValidation(entity); // nonsense
}
}
DbContext class:
public class CompanyMasterDataContext : DbContext {
public DbSet<PostOffice> PostOffices { get; set; }
public DbSet<Carrier> Carriers { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<CustomerCarrierLink> CustomerCarrierLinks { get; set; }
public UnitOfWork Unit { get; internal set; }
public CompanyMasterDataContext(DbContextOptions<CompanyMasterDataContext> options)
: base(options)
{
Unit = new UnitOfWork(this);
}
public void UpdateEntitiesByBaseModel(BaseModel baseModel)
{
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Added:
entry.CurrentValues["CompanyId"] = baseModel.CompanyId;
entry.CurrentValues["CreatedDateTime"] = DateTime.Now;
entry.CurrentValues["CreatedBy"] = baseModel.CreatedBy;
entry.CurrentValues["IsDeleted"] = false;
entry.CurrentValues["IsActive"] = true;
Unit.OnBeforeChildEntityProcessed(entry.Entity, enumEntityProcessState.Add);
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["ChangedDateTime"] = DateTime.Now;
entry.CurrentValues["ChangedBy"] = baseModel.ChangedBy;
entry.CurrentValues["IsDeleted"] = true;
Unit.OnBeforeChildEntityProcessed(entry.Entity, enumEntityProcessState.Delete);
break;
case EntityState.Modified:
if (entry.Entity != null && entry.Entity.GetType() != typeof(Company))
entry.CurrentValues["CompanyId"] = baseModel.CompanyId;
entry.CurrentValues["ChangedDateTime"] = DateTime.Now;
entry.CurrentValues["ChangedBy"] = baseModel.ChangedBy;
Unit.OnBeforeChildEntityProcessed(entry.Entity, enumEntityProcessState.Update);
break;
}
}
}
}
DiscoveryClass:
public abstract class UnitOfWorkDiscoverySet
{
private Dictionary<Type, object> Repositories { get; set; }
private CompanyMasterDataContext _context;
public UnitOfWorkDiscoverySet(CompanyMasterDataContext context)
{
_context = context;
InitializeSets();
}
private void InitializeSets()
{
var discoverySetType = GetType();
var discoverySetProperties = discoverySetType.GetProperties();
Repositories = new Dictionary<Type, object>();
foreach (var child in discoverySetProperties)
{
var childType = child.PropertyType;
var repositoryType = childType.GetInterfaces()
.Where( i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRepository<>))
.FirstOrDefault();
if (repositoryType != null)
{
var repositoryModel = repositoryType.GenericTypeArguments.FirstOrDefault();
if (repositoryModel != null)
{
if (repositoryModel.IsSubclassOf(typeof(BaseModel)))
{
var repository = InitializeProperty(child); //var repository = child.GetValue(this);
if (repository != null)
{
Repositories.Add(repositoryModel, repository);
}
}
}
}
}
}
private object InitializeProperty(PropertyInfo property)
{
if(property != null)
{
var instance = Activator.CreateInstance(property.PropertyType, new object[] {
_context, this
});
if(instance != null)
{
property.SetValue(this, instance);
return instance;
}
}
return null;
}
public void OnBeforeChildEntityProcessed(object childObject, enumEntityProcessState processState)
{
if(childObject != null)
{
var repository = GetRepositoryByObject(childObject);
var parameters = new object[] { childObject, processState };
InvokeRepositoryMethod(repository, "OnBeforeEntityProcessed", parameters);
}
}
public void ValidateChildren<Entity>(Entity entity, List<string> errorMessages) where Entity : BaseModel
{
var children = BaseModelUpdater.GetChildModels(entity);
if(children != null)
{
foreach(var child in children)
{
if(child != null)
{
if (child.GetType() == typeof(IEnumerable<>))
{
var list = (IEnumerable<object>) child;
if(list != null)
{
foreach (var childInList in list)
{
ValidateChild(childInList, errorMessages);
}
}
}
ValidateChild(child, errorMessages);
}
}
}
}
public void ValidateChild(object childObject, List<string> errorMessages)
{
if(childObject != null)
{
var repository = GetRepositoryByObject(childObject);
var parameters = new object[] { childObject, errorMessages };
InvokeRepositoryMethod(repository, "Validate", parameters);
}
}
public void InvokeRepositoryMethod(object repository, string methodName, object[] parameters)
{
if (repository != null)
{
var methodToInvoke = repository.GetType().GetMethod(methodName);
var methods = repository.GetType().GetMethods().Where(x => x.Name == methodName);
if (methodToInvoke != null)
{
methodToInvoke.Invoke(repository, parameters);
}
}
}
public object GetRepositoryByObject(object objectForRepository)
{
return Repositories?[objectForRepository.GetType()];
}
public object GetObject<Entity>(Type type, Entity entity) where Entity : BaseModel
{
var childObjects = BaseModelUpdater.GetChildModels(entity);
foreach (var childObject in childObjects)
{
if (childObject.GetType().FullName == type.FullName)
{
return childObject;
}
}
return null;
}
}
}
The problem:
I want to validate data in every model and the childmodels properties/list, know you might say this can be done using attributes, but the validation can be fairly complex and i prefer to separate this in it's own space.
The way i have attacked this problem is by using reflection from the UnitDiscoverySet class, here i can find every child of the Entity i'm trying to process and search for an appropriate Repository containg the UnitOfWork. This works by all means, just need some more work and cleanup, but for some reason i feel like this is the cheating/wrong way to attack the problem, and i'm also not getting the compile-time errors + reflection comes at a cost.
I could validate children of the entity in the entity repository, but then i would be repeating myself all over the place, and this solution don't seem right either.
I don't want this solution to depend to heavy on entityframework, since it's not given that we will use this forever.
This solution also heavly depends on the UpdateEntitiesByBaseModel method inside The DbContext. So it only changes the fields that should be changed.
Not sure that i addressed this problem as good as thought i would, but i appreciate every contribution that will lead me down the right path. Thank you!
Solution(Edit):
I ended up only using the navigation properties for GET operations, and excluded it for insert operations. Made everything more flexible and faster, this way i didn't need to use the EF Tracker, which made an insert operation of 5000 entities from a 13 minute operation, down to 14.3 seconds.
This question would probably be best asked in CodeReview rather than SO which is geared towards specific code-related questions. You can ask 10 different developers and get 10 different answers. :)
Reflection definitely does have a cost, and it's not something I like to use very much at all.
I don't want this solution to depend to heavy on entityframework,
since it's not given that we will use this forever.
This is a fairly common theme I see in applications and frameworks that development teams I work with try to cope with when working with ORMs. To me, abstracting EF from the solution is like trying to abstract away parts of .Net. There is literally no point because you forfeit access to much of the flexibility and capability that Entity Framework offers. It leads to more, complex code to deal with things that EF can do natively leaving room for bugs as you re-invent the wheel, or leaving gaps that later have to be worked around. You either trust it, or you shouldn't use it.
I could validate children of the entity in the entity repository, but
then i would be repeating myself all over the place, and this solution
don't seem right either.
This is actually the pattern I've come to advocate for with projects. Many people are against the Repository pattern, but it is a great pattern to serve as a boundary for the domain for testing purposes. (No need to set up in-memory databases or attempt to mock a DbContext/DbSets) However, IMO the Generic Repository pattern is an anti-pattern. It separates entities concerns from one another, however in many cases we are dealing with entity "graphs" not individual entity types. Rather than defining repositories per entity, I opt for something that is effectively a repository per controller. (With repositories for truly common entities such as lookups for example.) There are two reasons behind this:
Fewer dependency references to pass around / mock
Better serves SRP
Avoid pigeon-holing DB operations
The biggest problem I have with generic or per-entity repositories is that while they appear to be conforming to SRP (responsible for operations for a single Entity) I feel they violate it because SRP is about having only one reason to change. If an I have an Order entity and an Order repository I may have several areas of the application that load and interact with orders. Methods to interact with Order entities now are called in several different places, and that forms many potential reasons for methods to be adjusted. You end up either with complex, conditional code, or several very similar methods to serve specific scenarios. (Orders for listing orders, orders by customer, orders by store, etc.) When it comes to validating entities, this is commonly done in context to the entire graph so it makes sense to centralize this in code related to the graph rather than individual entities. This goes for generic base operations like Add/Update/Delete. 80% of the time this works and saves effort, but that remaining 20% either has to get shoe-horned into the pattern, leading to inefficient and/or error-prone code, or work-arounds. K.I.S.S. should always trump D.N.R.Y when it comes to software design. Consolidation into base classes and the like is an optimization that should be done as the code evolves when "identical" functionality is identified. When it's done up-front as an architecture decision, I consider this premature optimization which creates obstacles for development, performance issues, and bugs when "similar" but not "identical" behaviour is grouped together leading to conditional code creeping in for edge cases.
So instead of an OrderRepository to serve orders, if I have something like a ManageOrderController, I will have a ManageOrderRepository to serve it.
For instance I like to use DDD styled methods for managing entities where my repositories play a part in the construction since they are privy to the data domain and can validate/retrieve related entities. So a typical repository would have:
IQueryable<TEntity> GetTEntities()
IQueryable<TEntity> GetTEntityById(id)
IQueryable<TRelatedEntity> GetTRelatedEntities()
TEntity CreateTEntity({all required properties/references})
void DeleteTEntity(entity)
TChildEntity CreateTChildEntity(TEntity, {all required properties/references})
Retrieval methods, including a "By ID" as it's a common scenario, return IQueryable so that callers can control how the data is consumed. This negates the need to try and abstract the Linq capabilities that EF can leverage so callers can apply filters, perform paging, sorting, then consume the data how they need. (Select, Any, etc.) The repository enforces core rules such as IsActive, and tenancy/authorization checks. This serves as a boundary for tests since mocks just have to return List<TEntity>.AsQueryable() or wrapped with a async-friendly collection type. (Unit-testing .ToListAsync() using an in-memory) The repository also serves as a go-to place for retrieving any related entities via whatever criteria is applicable. This can be viewed as potential duplication but changes to this repository will only be needed when the controller/view/area of the application needs to change. Common stuff like lookups would be pulled via their own repository. This cuts down on the need for lots of individual repository dependencies. Each area takes care of itself so changes/optimizations here don't need to consider or impact other areas of the application.
"Create" methods manage the rules around creating and associating entities to the Context to ensure that entities are always created in a minimally complete and valid state. This is where validation comes into play. Any value that is not null-able gets passed in, along with FKs (keys or references) needed to ensure that if SaveChanges() was the next call after Create, the entity would be valid.
"Delete" methods similarly come in here to manage validating data state/authorization, and apply consistent behaviour. (hard vs. soft delete, auditing, etc.)
I don't use "Update" methods. Updates are handled by DDD methods on the entity itself. Controllers define the unit of work, use the repository to retrieve the entity(ies), call the entity methods, then commit the unit of work. Validation can be done at the entity level, or via a Validator class.
In any case, that is just a summary of one approach out of 10+ you may get out there, and hopefully highlights some things to consider with whatever approach you take. My emphasis when it comes to working with EF would be:
Keep it simple. (K.I.S.S. > D.N.R.Y)
Leverage what EF has to offer rather than attempting to hide it.
Complex, clever code ultimately leads to more code, and more code leads to bugs, performance issues, and makes it difficult to adjust for requirements you haven't thought of up-front. (Leading to more complexity, more conditional paths, and more headaches) Frameworks like EF have been tested, optimized, and vetted so leverage them.
I am building a relatively simple webapp, but have run into a bit of a problem. After having searched low and high, I can't seem to find anyone with similar issues.
So the situation:
I have an entity:
public class Entity
{
[Key]
public int EntityId { get; set;}
public string EntityName { get; set; }
public virtual ICollection<OtherEntity> OtherEntities { get; set; }
}
Reason for the virtual ICollection<OtherEntity> is a many to many relationship between the two.
My DbContext:
public class WebAppDB : IdentityDbContext<ApplicationUser>
{
public DbSet<Entity> Entities{ get; set; }
public WebAppDB () : base("DefaultConnection")
{
}
public static WebAppDB Create()
{
return new WebAppDB();
}
}
}
The default connection goes to an Amazon RDS SQL Server Express.
Around this I have created a repository:
public interface IEntityRepository
{
IQueryable<Entity> AlEntitiss { get; }
IQueryable<Entity> AllEntitissIncluding(params Expression<Func<Entity, object>>[] includeProperties);
Entity FindEntity(int id);
void InsertOrUpdateEntity(Entity entity);
void DeleteEntity(int id);
void Save();
}
public class EntityRepository : IEntityRepository
{
// handle to the database through the O.R.M. system.
private WebAppDB context = new WebAppDB();
public IQueryable<Entity> AllCEntitys
{
get { return context.Entitiss; }
}
public IQueryable<Entity> AllClassesIncluding(params Expression<Func<Entity, object>>[] includeProperties)
{
IQueryable<Entity> query = context.Entitiss;
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query;
}
public Entity FindEntity(int id)
{
return context.Entitiss.Find(id);
}
public void InsertOrUpdateEntity(Entity Entity)
{
if (Entity.EntityId == 0)
{
context.Entitiss.Add(Entity);
}
else
{
context.Entry(Entity).State = EntityState.Modified;
}
}
public void DeleteEntity(int id)
{
Entity Entity = FindEntity(id);
context.Entitys.Remove(Entity);
}
public void Save()
{
context.SaveChanges();
}
}
Now, the problem lies in the fact that I can get a list of the entities I have in the database and load them just fine into Selectlists or similar structures.
I can also get specific Entities based on other properties than the primary key.
But I can't get a single entity from the database using the primary key as my entry point. I have tried using DbSet.Entities.SingleOrDefault(), .Single(), .Find()
I have also tried to retrieve it using
DbSet.Entities.ToList().Where(x => x.EntityId == id)
I have moved from 2 data contexts down to 1, I have checked that I am referencing the same version of the EF in all projects in the solution (have a separate project for the entities).
After having read This, I fiddled with the Lazy loading, but no effect.
So I am running out of ideas and places to look.
If you need more information I will provide it to the best of my ability.
Thank you for reading and helping.
Update1:
I have tried moving to a local database with no luck, and it seems what I earlier described with loading lists of Entities working was exaggerated. They too don't load consistently when called.
Update 2:
Having used the SQL profiler to determine that not all calls from the controllers where executed on the database, I tried transplanting the entities to a fresh MVC project. Unfortunately the problem persists and I am out of ideas as this point.
Update 3:
After some more investigation and some help from Gert Arnold it has turned out that I misdiagnosed the initial problem. Due to poor coding practices it was not 1 central problem, but several separate problems all exhibiting similar characteristics that led me to the wrong conclusion that the EF was at fault. I am sorry to have wasted anyones time with this.
public Class FindEntity(int id)
{
//Want to find a specific student
return context.Entitys.Find(id);
}
This one seems a bit weird to me, what's the Class? I think it should be Student. Anyway, check out the code sample below:
public Entity FindEntity(int id)
{
return context.Entities.FirstOrDefault(e => e.EntityId == id); // note that it will return null if not found.
}
Multi-select lists in MVC don't seem to bind to complex models. Instead, they return an array of selected Id numbers.
I have such a control on a page, and I'm annoyed by the amount of conditional logic I've had to deploy to get it to work. The objects in question are a Staff object who can have TeamMember membership of none or more teams.
My objects are from entity framework. I added this to the Staff object:
public int[] SelectedTeamMembers
{
get; set;
}
I can now bind to this property in my View, and users can edit the multiselect list. On posting back the edit form, I have to do this (comments added for clarity):
//user.TeamMembers not bound, so get existing memberships
IEnumerable<TeamMember> existingTeamMembers = rep.TeamMembers_Get().Where(t => t.UserId == user.UserID);
//if array is empty, remove all team memberships & avoid null checks in else
if(user.SelectedTeamMembers == null)
{
foreach(TeamMember tm in existingTeamMembers)
{
rep.TeamMembers_Remove(tm);
}
}
else
{
// if team members have been deleted, delete them
foreach (TeamMember tm in existingTeamMembers)
{
if (!user.SelectedTeamMembers.Contains(tm.TeamId))
{
rep.TeamMembers_Remove(tm);
}
}
// if there are new team memberships, add them
foreach (int i in user.SelectedTeamMembers)
{
if (!existingTeamMembers.Select(t => t.TeamId).Contains(i))
{
TeamMember tm = new TeamMember { TeamId = i, UserId = user.UserID };
rep.TeamMembers_Change(tm);
}
}
}
I can tidy this up a bit by farming out each bit to a function, of course, but it still feels like a sledgehammer to crack a nut.
Is there a neater way of achieving this?
You should evaluate the possibility of combining your for and foreach loops into a single loop as the first step of simplifying this code.
Also, you know how to use LINQ (as evidenced by you initial Where() statement) so simplify the null conditional action as well, using LINQ and some of its helper extensions:
//user.TeamMembers not bound, so get existing memberships
IEnumerable<TeamMember> existingTeamMembers = rep.TeamMembers_Get().Where(t => t.UserId == user.UserID);
//if array is empty, remove all team memberships & avoid null checks in else
if(user.SelectedTeamMembers == null)
{
existingTeamMembers.ToList().ForEach(tm => rep.TeamMembers_Remove(tm));
}
else
{
// if team members have been deleted, delete them
existingTeamMembers.Where(tm => !user.SelectedTeamMembers.Contains(tm.TeamId)).ToList().ForEach(tm => rep.TeamMembers_Remove(tm));
// if there are new team memberships, add them
user.SelectedTeamMembers.Except(existingTeamMembers.Select(t=> t.TeamId)).ToList().ForEach(i =>
{
TeamMember tm = new TeamMember { TeamId = i, UserId = user.UserID };
rep.TeamMembers_Change(tm);
});
}
While this has not decreased the conditional complexity (as in all the conditionals are still there) the syntax is a lot more readable.
You could do it this way...It relies on using the RemoveRange method.
Entity - I'm using my own for demo purposes
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public String Name { get; set; }
}
Action
public ActionResult Action(Guid[] selectedTeamMembers)
{
using (var ctx = new DatabaseContext())
{
//
// Start by targeting all users!
//
var usersToRemove = ctx.Users.AsQueryable();
//
// if we have specified a selection then select the inverse.
//
if (selectedTeamMembers != null)
{
usersToRemove = usersToRemove.Where(x => !selectedTeamMembers.Contains(x.Id));
}
//
// Use the Set Generic as this gives us access to the Remove Range method
//
ctx.Set<User>().RemoveRange(usersToRemove);
ctx.SaveChanges();
}
return View();
}
Hope this helps.
What is the correct way to save a graph of objects whose state you don't know? By state I mean whether they are new or existing database entries that are being updated.
For instance, if I have:
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
public virtual ICollection<Automobile> Automobiles { get; set; }
}
public class Automobile
{
public int Id { get; set; }
public int Name { get; set; }
public short Seats { get; set; }
public virtual ICollection<MaintenanceRecord> MaintenanceRecords { get; set ;}
public virtual Person Person { get; set; }
}
public class MaintenanceRecord
{
public int Id { get; set; }
public int AutomobileId { get; set; }
public DateTime DatePerformed { get; set; }
public virtual Automobile Automobile{ get; set; }
}
I'm editing models, similar to these objects above, and then passing those models into the data layer to save, where for this instance I happen to be using entity framework. So I'm translating these models into POCO entities internal to the DAL.
It appears that unless my models have a state indicating whether they are new or updated, I have quite a bit of work to do to "Save" the changes. I have to first select the Person entity, update it, then match any existing Automobiles and update those and add any new, then for each automobile check for any new or updated maintenance records.
Is there a faster/easier way of doing this? It's possible I can keep track of the Model state, which I guess would be helpful with this, but it would mean changes to code outside of the data layer which i would prefer to avoid. I'm just hoping there is a pattern of usage out there that I can follow for updates like this.
I ran into this issue a while back and have been following this thread on the EF Codeplex site. https://entityframework.codeplex.com/workitem/864
Seems like it is being considered for the next release, I'm assuming EF 7, which apparently is a pretty large internal overhaul of EF. This may be worth checking out... http://www.nuget.org/packages/RefactorThis.GraphDiff/
Back when I was working on this I found another EF post on SO, and someone had an example of how to do this manually. At the time I decided to do it manually, not sure why, GraphDiff looks pretty cool. Here is an example of what I did.
public async Task<IHttpActionResult> PutAsync([FromBody] WellEntityModel model)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var kne = TheContext.Companies.First();
var entity = TheModelFactory.Create(model);
entity.DateUpdated = DateTime.Now;
var currentWell = TheContext.Wells.Find(model.Id);
// Update scalar/complex properties of parent
TheContext.Entry(currentWell).CurrentValues.SetValues(entity);
//We don't pass back the company so need to attached the associated company... this is done after mapping the values to ensure its not null.
currentWell.Company = kne;
// Updated geometry - ARGHHH NOOOOOO check on this once in a while for a fix from EF-Team https://entityframework.codeplex.com/workitem/864
var geometryItemsInDb = currentWell.Geometries.ToList();
foreach (var geometryInDb in geometryItemsInDb)
{
// Is the geometry item still there?
var geometry = entity.Geometries.SingleOrDefault(i => i.Id == geometryInDb.Id);
if (geometry != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(geometryInDb).CurrentValues.SetValues(geometry);
else
// No: Delete it
TheContext.WellGeometryItems.Remove(geometryInDb);
}
foreach (var geometry in entity.Geometries)
{
// Is the child NOT in DB?
if (geometryItemsInDb.All(i => i.Id != geometry.Id))
// Yes: Add it as a new child
currentWell.Geometries.Add(geometry);
}
// Update Surveys
var surveyPointsInDb = currentWell.SurveyPoints.ToList();
foreach (var surveyInDb in surveyPointsInDb)
{
// Is the geometry item still there?
var survey = entity.SurveyPoints.SingleOrDefault(i => i.Id == surveyInDb.Id);
if (survey != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(surveyInDb).CurrentValues.SetValues(survey);
else
// No: Delete it
TheContext.WellSurveyPoints.Remove(surveyInDb);
}
foreach (var survey in entity.SurveyPoints)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != survey.Id))
// Yes: Add it as a new child
currentWell.SurveyPoints.Add(survey);
}
// Update Temperatures - THIS IS A HUGE PAIN = HOPE EF is updated to handle updating disconnected graphs.
var temperaturesInDb = currentWell.Temperatures.ToList();
foreach (var tempInDb in temperaturesInDb)
{
// Is the geometry item still there?
var temperature = entity.Temperatures.SingleOrDefault(i => i.Id == tempInDb.Id);
if (temperature != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(tempInDb).CurrentValues.SetValues(temperature);
else
// No: Delete it
TheContext.WellTemperaturePoints.Remove(tempInDb);
}
foreach (var temps in entity.Temperatures)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != temps.Id))
// Yes: Add it as a new child
currentWell.Temperatures.Add(temps);
}
await TheContext.SaveChangesAsync();
return Ok(model);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
return InternalServerError();
}
This is a huge pain to me too. I extracted the answer from #GetFuzzy to a more reusable method:
public void UpdateCollection<TCollection, TKey>(
DbContext context, IList<TCollection> databaseCollection,
IList<TCollection> detachedCollection,
Func<TCollection, TKey> keySelector) where TCollection: class where TKey: IEquatable<TKey>
{
var databaseCollectionClone = databaseCollection.ToArray();
foreach (var databaseItem in databaseCollectionClone)
{
var detachedItem = detachedCollection.SingleOrDefault(item => keySelector(item).Equals(keySelector(databaseItem)));
if (detachedItem != null)
{
context.Entry(databaseItem).CurrentValues.SetValues(detachedItem);
}
else
{
context.Set<TCollection>().Remove(databaseItem);
}
}
foreach (var detachedItem in detachedCollection)
{
if (databaseCollectionClone.All(item => keySelector(item).Equals(keySelector(detachedItem)) == false))
{
databaseCollection.Add(detachedItem);
}
}
}
With this method in place I can use it like this:
public void UpdateProduct(Product product)
{
...
var databaseProduct = productRepository.GetById(product.Id);
UpdateCollection(context, databaseProduct.Accessories, product.Accessories, productAccessory => productAcccessory.ProductAccessoryId);
UpdateCollection(context, databaseProduct.Categories, product.Categories, productCategory => productCategory.ProductCategoryId);
...
context.SubmitChanges();
}
However when the graph gets deeper, I have a feeling this will not be sufficient.
What your looking for is the Unit of Work pattern:
http://msdn.microsoft.com/en-us/magazine/dd882510.aspx
You can either track UoW on the client and pass it in with the DTO or have the server figure it out. Both the veritable DataSet and EF Entities have their own internal implementation of UoW. For something stand alone there is this framework, but I have never used it so have no feedback:
http://genericunitofworkandrepositories.codeplex.com/
Alternatively another option is to do real time updates with undo functionality, kind of like when you go into Gmail contacts and it saves the changes as you make them with the option to undo.
It depends HOW you are accomplishing adding/changing the entities.
I think you may be trying to do too much with an entity at any given time. Allowing editing and adding at the same time can get you into a situation where your not sure what is being done with the entity, especially in a disconnected scenario. You should only perform a single action on a single entity at a time, unless you are deleting entities. Does this seem monotonous, sure, but 99% of your users want a clean and easily understandable interface. Many time we end up making screens of our applications "god" screens where everything and anything can be done. Which 9/10 times isn't needed (YAGNI).
This way, when you edit a user, you know you are doing an update operation. If you are adding a new maintenance record, you know you are creating a new record that is attached to an automobile.
To summarize, you should limit how many operations you are making available for a single screen and make sure you provide some type of unique information for the entity so you can try to look up the entity to see if it exists.
I had the similar problem, and couldnt find my own solution. I think that problem is complex. Complete solution for updating graphs in disconected scenario with EF6 I find in extension method RefactoringThis.GraphDiff produced by Brent McKendric.
Exemple brings by author is:
using (var context = new TestDbContext())
{
// Update the company and state that the company 'owns' the collection Contacts.
context.UpdateGraph(company, map => map
.OwnedCollection(p => p.Contacts, with => with
.AssociatedCollection(p => p.AdvertisementOptions))
.OwnedCollection(p => p.Addresses)
);
context.SaveChanges();
}
See more at:
http://blog.brentmckendrick.com/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/