I have an Asp.Net project with Entity Framework 7 an i have a Email class with a list of attachments.
I don't want to leave that anyone add a item to my list, them i have
private List<Attachment> Resources { get; set; }
public IEnumerable<Attachment> Attachments { get; set; }
Now, I want to map to database the relationship with the property Resources instead Attachments.
Entity Framework 7 rise an Exception...
How i can do this.
Separate the this to two different model, one internal that maps to the database and another one that's available to users.
It's also the correct way of passing data between layers.
Hope it helps!
I agree with Itay.
Maybe this code example could help you.
Make entities that map to db tables.
public class EmailState
{
public int Id { get; private set; }
public List<AttachmentState> Resources { get; set; }
public static Email ToEmail(EmailState state)
{
return new Email(state);
}
}
public class AttachmentState
{
public static Attachment ToAttachment(AttachmentState state)
{
return new Attachment(state);
}
public Attachment ToAttachment()
{
return new Attachment(this);
}
}
Make classes that are available to users
public class Email
{
public Email()
{
this.State = new EmailState();
}
internal Email(EmailState state)
{
this.State = state;
}
internal EmailState State { get; set; }
public int Id { get; private set; }
public IEnumerable<Attachment> Attachments()
{
return this.State.Resources.Select(x => x.ToAttachment());
}
public void AddAttachment(Attachment attachment)
{
this.State.Resources.Add(attachment.State);
}
}
public class Attachment
{
public Attachment()
{
this.State = new AttachmentState();
}
internal Attachment(AttachmentState state)
{
this.State = state;
}
internal AttachmentState State { get; set; }
}
Define DbContext
public class EmailDbContext : DbContext
{
public DbSet<EmailState> Emails { get; set; }
public DbSet<AttachmentState> Attachments { get; set; }
}
Make repository
public interface IEmailRepository
{
void Add(Email email);
Email GetById(int emailId);
}
public class EmailRepository : IEmailRepository
{
private EmailDbContext _context;
public EmailRepository(EmailDbContext context)
{
_context = context;
}
public void Add(Email email)
{
_context.Emails.Add(email.State);
}
public Email GetById(int emailId)
{
EmailState emailState = _context.Emails.Single(x => x.Id == emailId);
return new Email(emailState);
}
}
Use it like this
using (var context = new EmailDbContext())
{
IEmailRepository repository = new EmailRepository(context);
var email = new Email();
repository.Add(email);
context.SaveChanges();
var emailFoundById = repository.GetById(email.Id);
}
Related
Im currently trying to implement CRUD functionality with a dbfactory and generics with microsoft EF, but while listing entries is working, making changes to the db is currently not working.
public class AbstractDataModel
{
[Key]
public Guid gid { get; set; }
}
Model
class SalesOrder : AbstractDataModel
{
public int salesOrderID { get; set; }
public int productID { get; set; }
public int customerID { get; set; }
public Guid createdBy { get; set; }
public string dateCreated { get; set; }
public string orderDate { get; set; }
public string orderStatus { get; set; }
public string dateModified { get; set; }
}
A DBCore with some other functionality besides the ones listed here, which are not relevant for the factory
public class DBCore : DbContext
{
public static string connectionString = "myConnectionStringToDb";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
Data Service which calls factory
class SalesOrderService : DBCore
{
public DbSet<SalesOrder> SalesOrders { get; set; }
public OkObjectResult GetAllSalesOrders()
{
DBFactory factory = new DBFactory();
return new OkObjectResult(JsonConvert.SerializeObject(factory.GetAll(SalesOrders)));
}
public OkObjectResult AddSalesOrder(SalesOrder order)
{
order.gid = Guid.NewGuid();
return DBFactory.AddOne(order);
}
public OkObjectResult UpdateSalesOrder(SalesOrder order)
{
return DBFactory.UpdateOne(order);
}
public OkObjectResult DeleteSalesOrder(SalesOrder order)
{
return DBFactory.DeleteOne(order);
}
}
simple CRUD-Factory,
class DBFactory : DBCore
{
public DbSet<UserModel> Users { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
public List<T> GetAll<T>(DbSet<T> dbset) where T : class
{
using (this)
{
return dbset.ToList();
}
}
public static OkObjectResult AddOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Add(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully added");
}
}
public static OkObjectResult UpdateOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Update(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully updated");
}
}
public static OkObjectResult DeleteOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Attach(data);
factory.Remove(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully removed");
}
}
}
Edit: Following the advices i changed the code so it should SaveChanges for the Factory, which also contains the context as a property. But it still doesnt seem to work for all database operations except listing all entries
Editv2: Thanks for the adivces it seems i have solved that problem, but a new one appeared :D
I can now do database operations like deleting entries, but now i cant list the entries anymore because the following error occurs, although the code there didnt really change:
"Executed 'GetAllOrders' (Failed, Id=5fb95793-572a-4545-ac15-76dffaa7a0cf, Duration=74ms)
[2020-10-23T14:33:43.711] System.Private.CoreLib: Exception while executing function: GetAllOrders. Newtonsoft.Json: Self referencing loop detected for property 'Context' with type 'FicoTestApp.Models.SalesOrder'. Path '[0].ChangeTracker'."
try adding
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
to your
startup.cs
it should to the job
I have the following entities: PushTemplate and PushTemplateMessage. One PushTemplate can have many PushTemplateMessages. I have repositories for this. All works for the create operation. But problem starts when I try to update PushTemplate and set new text for messages. Insetad of an update I see new one PushTemplateMessage. I'll show my code.
Entity PushTemplate:
public class PushTemplate
{
public int PushTemplateId { get; set; }
[Required]
public List<PushTemplateMessage> Messages { get; set; }
public DateTime CreatedAt { get; set; }
public PushTemplate()
{
CreatedAt = DateTime.UtcNow;
}
}
Entity PushTemplateMessage:
public class PushTemplateMessage
{
public int PushTemplateMessageId { get; set; }
public string PushTitle { get; set; }
public string PushMessage { get; set; }
public PushTemplate PushTemplate { get; set; }
}
Repository PushTemplateRepository:
public class PushTemplateRepository : IPushTemplateRepository
{
private readonly ApplicationDbContext _applicationContext;
public PushTemplateRepository(ApplicationDbContext applicationContext)
{
_applicationContext = applicationContext;
}
public IQueryable<PushTemplate> PushTemplates => _applicationContext.PushTemplates;
public void Save(PushTemplate pushTemplate)
{
if (pushTemplate.PushTemplateId == 0)
{
_applicationContext.PushTemplates.Add(pushTemplate);
}
else
{
PushTemplate dbEntity = _applicationContext.PushTemplates.Find(pushTemplate.PushTemplateId);
dbEntity.Messages = new List<PushTemplateMessage>();
_applicationContext.SaveChanges();
dbEntity.Messages = pushTemplate.Messages;
}
_applicationContext.SaveChanges();
}
}
Database context:
public class ApplicationDbContext : IdentityDbContext
{
private readonly string _connectionString;
public DbSet<PushTemplate> PushTemplates { get; set; }
public DbSet<PushTemplateMessage> TemplateMessages { get; set; }
public ApplicationDbContext(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("MakeAppDb");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_connectionString, b => b.MigrationsAssembly("MakeAppPushesNet_2"));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PushTemplate>()
.HasMany(x => x.Messages)
.WithOne(y => y.PushTemplate)
.OnDelete(DeleteBehavior.Cascade);
}
}
And finally call to update operation from my controller:
PushTemplate pushTemplate = new PushTemplate
{
Messages = pushTemplateMessages // new list of messages
};
_pushTemplateRepository.Save(pushTemplate);
After this operation I have the old PushTemplateMessage and new version of PushTemplateMessage. But I need only new one! As you can see, in the repository I have tried to 'clean' old PushTemplateMessages to set then new list. But it continues to merge old data with new! Where is the mistake?
Ok, I have solved it. This post was helpful. This is from the answer.
So, if you want to synchronize (not add), even if you want set new values as NULL you have to use include("EntityName") on your db context. Now my save/update repository code is:
public void Save(PushTemplate pushTemplate)
{
if (pushTemplate.PushTemplateId == 0)
{
_applicationContext.PushTemplates.Add(pushTemplate);
}
else
{
PushTemplate dbEntity = _applicationContext.PushTemplates
.Include(x => x.Messages)
.FirstOrDefault(x => x.PushTemplateId == pushTemplate.PushTemplateId);
dbEntity.Messages = pushTemplate.Messages;
}
_applicationContext.SaveChanges();
}
As you can see I just use Include(). And now I can set null or synchronize it with a new list of values.
I have two sets of objects
Objects that I use in C# client application:
public class EmployeeClient
{
public int Id { get; set; }
public int DepartmentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class DepartmentClient
{
public int Id { get; set; }
public string Name { get; set; }
}
public class OrganizationClient
{
public int Id { get; set; }
public string Name { get; set; }
public List<DepartmentClient> Departments { get; set; }
public List<EmployeeClient> Employees { get; set; }
}
And DTOs:
public class EmployeeDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
public class DepartmentDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<EmployeeDto> Employees { get; set; }
}
public class OrganizationDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<DepartmentDto> Departments { get; set; }
}
I use AutoMapper and I need to configure mapping Client -> DTOs and DTOs -> Client.
I implemented mapping DTOs->Client like this:
public class DtoToClientMappingProfile: Profile
{
public DtoToClientMappingProfile()
{
CreateMap<EmployeeDto, EmployeeClient>();
CreateMap<DepartmentDto, DepartmentClient>();
CreateMap<OrganizationDto, OrganizationClient>()
.ForMember(dest => dest.Employees, opt => opt.ResolveUsing(src => src.Departments.SelectMany(d => d.Employees)))
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationDto dto, OrganizationClient client)
{
foreach (var department in dto.Departments)
{
foreach (var employee in department.Employees)
{
var clientEmployee = client.Employees.First(e => e.Id == employee.Id);
clientEmployee.DepartmentId = department.Id;
}
}
}
}
It is not universal solution, but is works for me.
I've found only one option how mapping Client->DTOs could be implemented:
public class ClientToDtosMappingProfile : Profile
{
public ClientToDtosMappingProfile()
{
CreateMap<EmployeeClient, EmployeeDto>();
CreateMap<DepartmentClient, DepartmentDto>();
CreateMap<OrganizationClient, OrganizationDto>()
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationClient client, OrganizationDto dto)
{
foreach (var employee in client.Employees)
{
var departmentDto = dto.Departments.First(d => d.Id == employee.DepartmentId);
if (departmentDto.Employees == null)
{
departmentDto.Employees = new List<EmployeeDto>();
}
var configuration = (IConfigurationProvider)new MapperConfiguration(cfg =>
{
cfg.AddProfiles(typeof(ClientToDtosMappingProfile));
});
var mapper = (IMapper)new Mapper(configuration);
var employeeDto = mapper.Map<EmployeeDto>(employee);
departmentDto.Employees.Add(employeeDto);
}
}
}
It works, but I do not like this solution because I should create instance of new Mapper every time I map objects. In my real code Employee has a lot of nested elements and mapping is configured in multiple profiles.
Any ideas how it could be implemented better?
I made my code a bit better using ResolutionContext. It allows not to create mappers in AfterMap function.
DtoToClientMappingProfile:
public class DtoToClientMappingProfile: Profile
{
public DtoToClientMappingProfile()
{
CreateMap<EmployeeDto, EmployeeClient>();
CreateMap<DepartmentDto, DepartmentClient>();
CreateMap<OrganizationDto, OrganizationClient>()
.ForMember(dest => dest.Employees, opt => opt.Ignore())
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationDto dto, OrganizationClient client, ResolutionContext resolutionContext)
{
if (dto.Departments == null)
{
return;
}
client.Departments = new List<DepartmentClient>();
foreach (var department in dto.Departments)
{
var departmentClient = resolutionContext.Mapper.Map<DepartmentClient>(department);
client.Departments.Add(departmentClient);
if (department.Employees == null)
{
continue;
}
if (client.Employees == null)
{
client.Employees = new List<EmployeeClient>();
}
foreach (var employee in department.Employees)
{
var employeeClient = resolutionContext.Mapper.Map<EmployeeClient>(employee);
employeeClient.DepartmentId = department.Id;
client.Employees.Add(employeeClient);
}
}
}
ClientToDtosMappingProfile:
public class ClientToDtosMappingProfile : Profile
{
public ClientToDtosMappingProfile()
{
CreateMap<EmployeeClient, EmployeeDto>();
CreateMap<DepartmentClient, DepartmentDto>();
CreateMap<OrganizationClient, OrganizationDto>()
.AfterMap(AfterMap);
}
private void AfterMap(OrganizationClient client, OrganizationDto dto, ResolutionContext resolutionContext)
{
if (client.Employees == null)
{
return;
}
foreach (var employee in client.Employees)
{
var departmentDto = dto.Departments.First(d => d.Id == employee.DepartmentId);
if (departmentDto.Employees == null)
{
departmentDto.Employees = new List<EmployeeDto>();
}
var employeeDto = resolutionContext.Mapper.Map<EmployeeDto>(employee);
departmentDto.Employees.Add(employeeDto);
}
}
}
If you call AssertConfigurationIsValid, AM will complain about what it doesn't know how to map.
The problem seems to be that you don't have the information needed to fill the destination object in the source object.
You will need to add a resolver for each property AM complains about, like the ResolveUsing you already have, for example.
You also need to pass the extra information that's needed.
The result may not look good eventually because AM cannot rely on uniform objects to do its job, you have to tell it what to do.
Another way to go about it is to do the high level mapping in your own code and rely on AM only when the mapping is simple enough so AM can do it by itself. The more you customize AM, the less value you get from it.
I've created a generic EF repository. For every deletions I need to check if my entity has a specific interface and do some changes to some other entities before remove it. How would I do that?
I tried to crate a foreach like this but it's not working.
var entitiesToRemove = context.Set<TEntity>().Where(predicate).ToList();
foreach (var entityToRemove in entitiesToRemove)
{
///
}
My current remove method
public void Remove(Func<TEntity, bool> predicate)
{
context.Set<TEntity>()
.Where(predicate).ToList()
.ForEach(del => context.Set<TEntity>().Remove(del));
}
I believe you can tackle the problem via the Strategy pattern
What follows is just one possible approach and example. You'd have to take dependency injection into consideration as well (but you can adapt this idea)
/* Your Repository implementation would probably look like this */
public class GenericRepository<TEntity>
{
private readonly DbContext context;
private readonly RemoveStrategyFactory removeStrategyFactory;
public GenericRepository(DbContext context, RemoveStrategyFactory removeStrategyFactory)
{
this.context = context;
this.removeStrategyFactory = removeStrategyFactory;
}
public void Remove(Func<TEntity, bool> predicate)
{
var entitiesToRemove = context.Set<TEntity>()
.Where(predicate).ToList();
var removeStrategy = removeStrategyFactory.GetStrategy<TEntity>();
foreach (var entity in entitiesToRemove)
{
removeStrategy.BeforeRemove(entity);
context.Set<TEntity>().Remove(entity);
}
}
}
/* SAMPLE ENTITIES */
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public Customer Customer { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsArchived { get; set; }
}
/* SAMPLE STRATEGIES and FACTORY */
public abstract class RemoveStrategy<TEntity>
{
public abstract void BeforeRemove(TEntity entity);
}
public sealed class DoNothingRemoveStrategy<TEntity> : RemoveStrategy<TEntity>
{
public override void BeforeRemove(TEntity entity)
{
// Do nothing
}
}
public sealed class CustomerRemoveStrategy : RemoveStrategy<Customer>
{
public override void BeforeRemove(Customer customer)
{
// Mark all orders as archived
foreach (var order in customer.Orders)
{
order.IsArchived = true;
}
}
}
public class RemoveStrategyFactory
{
private readonly Lazy<Dictionary<Type, object>> _lazyStrategyMap;
public RemoveStrategyFactory()
{
_lazyStrategyMap = new Lazy<Dictionary<Type, object>>(InitializeStrategyMap);
}
public RemoveStrategy<TEntity> GetStrategy<TEntity>()
{
var strategyMap = _lazyStrategyMap.Value;
object strategy;
if (strategyMap.TryGetValue(typeof(TEntity), out strategy))
{
return (RemoveStrategy<TEntity>) strategy;
}
return new DoNothingRemoveStrategy<TEntity>();
}
public Dictionary<Type, object> InitializeStrategyMap()
{
return new Dictionary<Type, object>
{
// CAREFUL: for Customer type, it must be a RemoveStrategy<Customer> or derived instance
{ typeof (Customer), new CustomerRemoveStrategy() }
};
}
}
class InvoiceAppDBContext: DbContext
{
public InvoiceAppDBContext() : base("InvoiceDBContext")
{
}
public DbSet<Users> User { get; set; }
public DbSet<Invoice> Invoices { get; set; }
}
class DataBaseUserRespository : IUser
{
private InvoiceAppDBContext _dbContext;
public DataBaseUserRespository()
{
_dbContext = new InvoiceAppDBContext();
}
public IEnumerable<Users> _userList
{
get
{
return _dbContext.User; // this is not working _userlist is showing null when i run it
}
}
public void SaveUser(Users user)
{
if(user.UserId == 0)
{
_dbContext.User.Add(user);
}
else
{
Users userEntity = _dbContext.User.Find(user.UserId);
userEntity.Change(user);
}
_dbContext.SaveChanges();
}
}
so DBSet or DBcontext is not getting the data from the table and putting it in IEnumerable<Users> list.
here i am debugging the userlist(accountList) and it is not showing data
hopefully this explains what problem i am getting :)