My mode class:
public class AccountCustomer
{
public bool IsMain { get; set; }
public int AccountId { get; set; }
public Account Account { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
public class Account
{
public int Id { get; set; }
public strig No{ get; set; }
..other fields
public ICollection<AccountCustomer> AccCustomer { get; set; } = new List<AccountCustomer>();
}
public class Customer
{
public int Id { get; set; }
public strig Name{ get; set; }
..other fields
public ICollection<AccountCustomer> AccCustomer { get; set; } = new List<AccountCustomer>();
}
I found soluton here and i have implemented same later i found that it is for without extra column
Many to many ef core updaate
Please let meknow how to do update... Am using latest ef 5 preview
My code :
_context.Set<AccountCustomer>().UpdateLinks(ac => ac.AccountId, account.Id,
ac => ac.CustomerId, account.AccCustomer .Select(ac => ac.CustomerId));
Codes of ManyToMany Update Extensions
Remove the unselected item and add new item to list.
public static class Extensions
{
public static void TryUpdateManyToMany<T, TKey>(this DbContext db, IEnumerable<T> currentItems, IEnumerable<T> newItems, Func<T, TKey> getKey) where T : class
{
db.Set<T>().RemoveRange(currentItems.ExceptThat(newItems, getKey));
db.Set<T>().AddRange(newItems.ExceptThat(currentItems, getKey));
}
private static IEnumerable<T> ExceptThat<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKeyFunc)
{
return items
.GroupJoin(other, getKeyFunc, getKeyFunc, (item, tempItems) => new { item, tempItems })
.SelectMany(t => t.tempItems.DefaultIfEmpty(), (t, temp) => new { t, temp })
.Where(t => ReferenceEquals(null, t.temp) || t.temp.Equals(default(T)))
.Select(t => t.t.item);
}
}
Codes of Update ViewModel
The update view pass it to action via request.
public class AccountCustomerVM
{
public bool IsMain { get; set; }
public Account Account { get; set; }
public List<int> Customers { get; set; }
}
Codes of Update Action
[HttpPut]
public IActionResult Update(AccountCustomerVM accountCustomerVM)
{
var model = _context.Accounts.Include(x => x.AccCustomer).FirstOrDefault(x => x.Id == accountCustomerVM.Account.Id);
_context.TryUpdateManyToMany(model.AccCustomer, accountCustomerVM.Customers
.Select(x => new AccountCustomer
{
IsMain = accountCustomerVM.IsMain,
CustomerId = x,
AccountId = accountCustomerVM.Account.Id
}), x => x.CustomerId);
_context.SaveChanges();
return Ok();
}
Related
I'm trying to update an entity using entity framework but, everytime I try to do it, it raises an error saying that a nested entity the main class contains cannot be tracked.
These are my classes:
public abstract class BaseEntity
{
public int Id { get; set; }
}
public class Dashboard : BaseEntity
{
public int Order { get; set; }
public string Title { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<Submenu> Submenu { get; set; }
}
public class Submenu : BaseEntity
{
public int Order { get; set; }
public bool Enabled { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public string Descriptions { get; set; }
public virtual ICollection<Action> Actions { get; set; }
public int DashboardId { get; set; }
public virtual Dashboard Dashboard { get; set; }
}
public class Action : BaseEntity
{
public string Type { get; set; }
public string Label { get; set; }
public string Url { get; set; }
public string Extension { get; set; }
public virtual Submenu Submenu { get; set; }
public int SubmenuId { get; set; }
}
The one I am using to update is Dashboard, which contains the rest of the classes.
I'm trying to do it using a generic service layer and a generic repository that are defined this way:
public class GenericService<T> : IGenericService<T> where T : BaseEntity
{
private readonly IBaseRepository<T> baseRepository;
public GenericService(IBaseRepository<T> baseRepository)
{
this.baseRepository = baseRepository;
}
public async Task Update(T entity, T attachedEntity)
{
await baseRepository.Update(entity, attachedEntity);
}
}
public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
private readonly PortalContext dataContext;
private DbSet<T> DbSet { get; set; }
public BaseRepository(PortalContext context)
{
dataContext = context;
DbSet = dataContext.Set<T>();
}
public async Task Update(T entity, T attachedEntity)
{
dataContext.Entry(attachedEntity).State = EntityState.Detached;
DbSet.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
await dataContext.SaveChangesAsync();
}
}
And, at last but no least, this is the way I am configuring everything at Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PortalContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("PortalContext"))
);
services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>));
services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddTransient<Func<string, ClaimsPrincipal, IRoleCheck>>((serviceProvider) =>
{
return (controllerName, claimsPrincipal) =>
new RoleCheck(serviceProvider.GetRequiredService<IGenericService<Dossier>>(),
serviceProvider.GetRequiredService<IGenericService<DossierTemplate>>(),
serviceProvider.GetRequiredService<IGenericService<Dashboard>>(),
controllerName, claimsPrincipal);
});
}
What the application first does is calling the RoleCheck class to retrieve and filter the required entities and, after that, the user can update them.
When I call the update function at the controller
public async Task<ActionResult<Dashboard>> Put(int id, [FromBody] Dashboard dashboard)
{
var currentDashboard = await service.Get(id);
if (currentDashboard == null)
{
return NotFound();
}
await service.Update(dashboard, currentDashboard);
return Ok();
}
I always receive the next error at the repository:
error
Is there something I am doing wrong? I have been stuck with this for a week now...
Thanks in advance and sorry for the long text, but I wanted it to be clear.
I could finally solve it by adding .AsNoTracking() at the Get() method of my repository:
public async Task<T> Get(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> includes)
{
IQueryable <T> query = DbSet.AsNoTracking();
if (includes != null)
{
query = includes(query);
}
return await query.FirstOrDefaultAsync(m => m.Id == id);
}
This is my DTO
public class Part1
{
public Part1()
{
Value = new List<ValueList>();
}
public int Id{ get; set; }
public List<ValueList> Value { get; set; }
}
public class ValueList
{
public int Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
}
and this is my table structure and I want to save above dto into below dto using automapper
public class Part1
{
public int Id{ get; set; }
public int Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
}
Do you want to know how to make the list map to singel string in the new model?
You can check my demo, in mapper profile, define the three values to map:
public class AutoMapping : Profile
{
public AutoMapping()
{
CreateMap<Part1, Part1DTO>() //Part1DTO is your below model
.ForMember(x => x.Value1, m => m.MapFrom(src => src.Value[0].Value1))
.ForMember(x => x.Value2, m => m.MapFrom(src => src.Value[0].Value2))
.ForMember(x => x.Value3, m => m.MapFrom(src => src.Value[0].Value3));
}
}
Controller:
public IActionResult Index()
{
Part1 part1= new Part1()
{
Id=1,
Value=new List<ValueList> { new ValueList { Value1=1,Value2="b",Value3="c"} }
}; //define old model
var result = _mapper.Map<Part1DTO>(part1); // map to new
return View(result);
}
Don't forget to add this in ConfigureServices:
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
Result:
When I call the object model variable ,straight from the database, I receive all the needed data about the dependent entity: MenuItem.
When i use an automapper it displays the mapped MenuItem object as null.
Automapping for getAll and getAllById works.
Principle Entity: ItemCategory
DependentEntity: MenuItem
Please help.
Mapping Profile:
public class MappingProfile: Profile
{
public MappingProfile()
{
CreateMap<ItemCategory, ItemCategoryDto>();
CreateMap<ItemType, ItemTypeDto>();
CreateMap<ItemStatus, ItemStatusDto>();
CreateMap<MenuItem, MenuItemDto>();
//CreateMap<ItemCategory, ItemCategoryDto>();
//CreateMap<ItemType, ItemTypeDto>();
//CreateMap<ItemStatus, ItemStatusDto>();
}
}
ItemCategory Model class:
public partial class ItemCategory
{
public ItemCategory()
{
MenuItem = new HashSet<MenuItem>();
}
public Guid Id { get; set; }
public string Description { get; set; }
public virtual ICollection<MenuItem> MenuItem { get; set; }
}
MenuItem Model Class:
public partial class MenuItem
{
public MenuItem()
{
Menu = new HashSet<Menu>();
MenuItemAllergy = new HashSet<MenuItemAllergy>();
MenuItemIngredient = new HashSet<MenuItemIngredient>();
MenuItemPrice = new HashSet<MenuItemPrice>();
MenuItemSpecial = new HashSet<MenuItemSpecial>();
OrderMenuItem = new HashSet<OrderMenuItem>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid ItemCategoryId { get; set; }
public Guid ItemTypeId { get; set; }
public Guid ItemStatusId { get; set; }
public virtual ItemCategory ItemCategory { get; set; }
public virtual ItemStatus ItemStatus { get; set; }
public virtual ItemType ItemType { get; set; }
public virtual ICollection<Menu> Menu { get; set; }
public virtual ICollection<MenuItemAllergy> MenuItemAllergy { get; set; }
public virtual ICollection<MenuItemIngredient> MenuItemIngredient { get; set; }
public virtual ICollection<MenuItemPrice> MenuItemPrice { get; set; }
public virtual ICollection<MenuItemSpecial> MenuItemSpecial { get; set; }
public virtual ICollection<OrderMenuItem> OrderMenuItem { get; set; }
}
ItemCategoryDTO:
public class ItemCategoryDto
{
public Guid Id { get; set; }
public string Description { get; set; }
public IEnumerable<MenuItemDto> MenuItems { get; set; }
}
MenuItemDTO:
public class MenuItemDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
ItemCategory Interface:
public interface IItemCategory: IRepositoryBase<ItemCategory>
{
IEnumerable<ItemCategory> GetAllItemCategories();
ItemCategory GetItemCategoryById(Guid categoryId);
ItemCategory GetItemCategoryWithDetails(Guid categoryId);
}
ItemCategory Repository:
public class ItemCategoryRepository: RepositoryBase<ItemCategory>, IItemCategory
{
public ItemCategoryRepository(eWaiterTestContext repositoryContext)
: base(repositoryContext)
{
}
public IEnumerable<ItemCategory> GetAllItemCategories()
{
return FindAll()
.OrderBy(ic => ic.Description)
.ToList();
}
//Can Add Find By description same way
public ItemCategory GetItemCategoryById(Guid categoryId)
{
return FindByCondition(x => x.Id.Equals(categoryId))
.FirstOrDefault();
}
public ItemCategory GetItemCategoryWithDetails(Guid categoryId)
{
return FindByCondition(x => x.Id.Equals(categoryId))
.Include(z=>z.MenuItem)
.FirstOrDefault();
}
}
ItemCategory Controller to return ItemCategory with related menuItems:
[HttpGet("{id}/menuItem")]
public IActionResult GetItemCategoryWithDetails(Guid id)
{
try
{
var category = _repository.ItemCategory.GetItemCategoryWithDetails(id);
if (category == null)
{
_logger.LogError($"Category with id: {id}, hasn't been found in db.");
return NotFound();
}
else
{
_logger.LogInfo($"Returned owner with details for id: {id}");
var result = _mapper.Map<ItemCategoryDto>(category);
return Ok(result);
}
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong inside GetItemCategoryWithDetails action:
straight from the database{ex.Message}");
return StatusCode(500, "Internal server error");
}
}
Names of the properties must be the same, but you just skipped 's' for ItemCategory model
ItemCategory Model class:
public partial class ItemCategory {
public virtual ICollection<MenuItem> MenuItem { get; set; }
}
ItemCategoryDto Model class:
public partial class ItemCategoryDto {
public virtual ICollection<MenuItem> MenuItems { get; set; }
}
Or you can just setup AutoMapper like this :)
public class CategoryMapper: Profile
{
public CategoryMapper()
{
CreateMap<ItemCategory, ItemCategoryDto>()
.ForMember(x => x.MenuItems, x => x.MapFrom(src => src.MenuItem));
}
}
and inject it in Startup.cs
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new GemlemMapper());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
Hope it will help, also be aware that's all properties which you need to use should be Included by the Include().
I have a GenericRepository,Which CourseRepository inherits from it, and Entities returns a list of courses
public class CourseRepository : GenericRepositories<Course>, ICourseRepository
{
public CourseRepository(LearningSiteDbContext Context) : base(Context)
{
}
}
public class GenericRepositories<TEntity> : IGenericRepositories<TEntity> where TEntity : class, IEntity,new()
{
protected readonly LearningSiteDbContext context;
public DbSet<TEntity> Entities { get; set; }
public GenericRepositories(LearningSiteDbContext Context)
{
context = Context;
Entities = context.Set<TEntity>();
}
}
But When I run this handler In Razor Page
public async Task OnGetAsync(int Id, CancellationToken cancellationToken)
{
var selectedCourse = await courseRepository.Entities.FindAsync(Id,cancellationToken);
Model = mapper.Map<CourseEditVm>(selectedCourse);
}
I get the following error :
Entity type 'Course' is defined with a single key property, but 2 values were passed to the 'DbSet.Find' method
And this is Course entity class
public class Course
{
public Course()
{
}
public Course(DateTime CreateDate)
{
this.CreateDate = CreateDate;
}
public int Id {get;set;}
public string CourseTitle { get; set; }
public string CourseDescription { get; set; }
public decimal CoursePrice { get; set; }
public string ImageName { get; set; }
public string DemoFileName { get; set; }
public DateTime CreateDate { get; set; }
public DateTime? UpdateDate { get; set; }
public bool IsDeleted { get; set; }
//Foreign key
public int? CourseStatusId { get; set; }
public int? CourseLevelId { get; set; }
public Guid? CustomUserId { get; set; }
public int? CourseGroupId { get; set; }
//Navigations
public CourseStatus CourseStatus { get; set; }
public CourseLevel CourseLevel { get; set; }
public CustomUser CustomUser { get; set; }
public CourseGroup CourseGroup { get; set; }
public ICollection<CourseEpisod> CourseEpisods { get; set; }
public ICollection<Keyword> Keywordkeys { get; set; }
}
And it's Course Config
class CourseConfig : IEntityTypeConfiguration<Course>
{
public void Configure(EntityTypeBuilder<Course> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).ValueGeneratedOnAdd();
builder.Property(c => c.CourseTitle).HasMaxLength(50);
builder.Property(c => c.CourseDescription).HasMaxLength(400);
builder.Property(c => c.ImageName).HasMaxLength(255);
builder.Property(c => c.DemoFileName).HasMaxLength(255);
//Relations
builder.HasMany(c => c.Keywordkeys).WithOne(c => c.Course).HasForeignKey(c => c.CourseId);
builder.HasOne(c => c.CustomUser).WithMany(c => c.Courses).HasForeignKey(c => c.CustomUserId);
}
}
But when I run this code, I will no longer receive this error
var selectedCourse = await courseRepository.Entities.FirstOrDefaultAsync(c => c.Id == Id, cancellationToken);
What is the cause of this error? Is my code wrong? How should I fix this error?
You are using the wrong method, if you check here
FindAsync you can see that if you want to pass a cancellation token, you need to pass your keys as an array like this
.FindAsync(new object[]{id}, cancellationToken);
I have 2 entities.
public class Foo
{
public virtual int FooId {get; set; }
public virtual IList<Bar> Bars { get; set; }
public virtual DateTime Date { get; set; }
}
public class Bar
{
public virtual int BarId { get; set;}
public virtual byte[] Value { get; set; }
public virtual DateTime Date { get; set; }
public virtual IList<Foo> Foos { get; set; }
}
When I load Foo from the database by FooId, it is completely hydrated, when I navigate to Bar, it has the correct BarId and Date from the database, but Value is always a byte[0].
Why?
The database is a varbinary(300) column.
The value in the database if I do select * from Bar in Management Studio shows
BarId Value Date
1 0x20CF30467ABD 10/19/2011
Thoughts?
My mapping:
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
ToTable("Foo");
HasKey(m => m.FooId);
Property(m => m.Date);
HasMany(m => m.Bars)
.WithMany(l => l.Foos)
.Map(m =>
{
m.ToTable("FooBars");
m.MapLeftKey("FooId");
m.MapRightKey("BarId");
});
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
ToTable("Bar");
HasKey(m => m.BarId);
Property(m => m.Value);
Property(m => m.Date);
HasMany(m => m.Foos)
.WithMany(l => l.Bars)
.Map(m =>
{
m.ToTable("FooBars");
m.MapLeftKey("BarId");
m.MapRightKey("FooId");
});
}
}
I refactored your code a bit but I can't see your problem.
public class Foo
{
public Foo()
{
Bars = new List<Bar>();
}
#region Public Properties
public virtual IList<Bar> Bars { get; set; }
public virtual DateTime Date { get; set; }
public virtual int FooId { get; set; }
#endregion
}
public class Bar
{
#region Public Properties
public virtual int BarId { get; set; }
public virtual DateTime Date { get; set; }
public virtual IList<Foo> Foos { get; set; }
public virtual byte[] Value { get; set; }
#endregion
}
public class FooConfiguration : EntityTypeConfiguration<Foo>
{
public FooConfiguration()
{
HasKey(m => m.FooId);
HasMany(m => m.Bars)
.WithMany(l => l.Foos)
.Map(m =>
{
m.ToTable("FooBars");
m.MapLeftKey(f => f.FooId, "FooId");
m.MapRightKey(b => b.BarId, "BarId");
});
}
}
public class BarConfiguration : EntityTypeConfiguration<Bar>
{
public BarConfiguration()
{
HasKey(m => m.BarId);
}
}
When I do this I get the byte[] back from the database
using(var context = new FooBarContext())
{
var foo = new Foo();
foo.Date = DateTime.Now;
var bar = new Bar();
bar.Date = DateTime.Now;
bar.Value = UTF8Encoding.UTF8.GetBytes("string");
foo.Bars.Add(bar);
context.Foos.Add(foo);
context.SaveChanges();
}
using(var context = new FooBarContext())
{
var foos = context.Foos.Where(f => f.FooId == 1).ToList();
}
Make sure you are using the latest of EF.