How to disable changestate tracking for a sub-entity in boilerplate AppService - asp.net-core

I'm using aspnet core & ef core with boilerplate and would like to disable the changestate tracking for a sub-entity. How do I this this within AppService (ie AsyncCrudAppService).
For example:
Entity:
[Table("tblCategory")]
public class Category : FullAuditedEntity<int>, IMustHaveTenant
{
public const int MaxCodeLength = 128;
public const int MaxNameLength = 2048;
public virtual int TenantId { get; set; }
[Required]
[StringLength(MaxCodeLength)]
public virtual string Code { get; set; }
[Required]
[StringLength(MaxNameLength)]
public virtual string Name { get; set; }
[ForeignKey("GroupId")]
public virtual Group Group { get; set; }
[Required]
public virtual int GroupId { get; set; }
}
Dto:
[AutoMapFrom(typeof(Category))]
public class CategoryDto : FullAuditedEntityDto<int>
{
[Required]
[StringLength(Category.MaxCodeLength)]
public string Code { get; set; }
[Required]
[StringLength(Category.MaxNameLength)]
public string Name { get; set; }
[Required]
public int GroupId { get; set; }
[DisableValidation]
public GroupDto Group{ get; set; }
}
AppService Update Method:
public override async Task<CategoryDto> Update(CategoryDto input)
{
var cat = await _categoryManager.GetA(input.Id);
MapToEntity(input, cat);
//I'd like to disable the tracking of cat.Group here ?
await _categoryManager.UpdateA(cat);
return await Get(input);
}
I'd like to disable the change detection for cat.Group, how do I do this ?
Thanks in advance.

Using AsNoTracking when loading resolves the issue

Tracking can be skipped by adding .AsNoTracking() to the call.
For example:
var cat = await _yourDbContext.AsNoTracking().FirstAsync(m => m.Id == input.Id);
Which is fine for results that will not be edited during it's lifetime.

Related

How to get the discriminator from the id of an entitie?

I would like to now wether my id is an invoice or an individualinvoice
individualinvoice.cs
public class IndividualInvoice : Invoice {
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}
invoice.cs
public class Invoice {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
[Required]
public string Company { get; set; }
[Required]
public string Address { get; set; }
[Required]
public int HouseNumber { get; set; }
[Required]
public string Zipcode { get; set; }
[Required]
public string City { get; set; }
[Required]
public string Country { get; set; }
[Required]
public string VATNumber { get; set; }
public Customer Customer { get; set; }
[ForeignKey("Customer")]
[Required]
public string CustomerId { get; set; }
}
gingsengdbcontext.cs
public class GingsengDbContext : IdentityDbContext<GingsengUser> {
public DbSet<Gingseng> Gingsengs { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<IndividualInvoice> IndividualInvoices { get; set; }
public GingsengDbContext(DbContextOptions<GingsengDbContext> options) : base(options)
{
}
}
And here is my controller where i would like to know from the id if the id corresponds to an individialinvoice or just an invoice? is there any cleaner way than to use singleordefault?
public class InvoicesController : Controller {
private readonly GingsengDbContext context;
private readonly IMapper mapper;
public InvoicesController(GingsengDbContext context, IMapper mapper)
{
this.context = context;
this.mapper = mapper;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetInvoice(string id) {
}
}
Well, the only clean way which works with all EF Core supported inheritance models (currently TPH and TPT) is to use C# is operator. However the classes must not inherit other non abstract class from the same hierarchy like in your example, because IndividualInvoice is a Invoice, hence will be included in DbSet<Invoice> and any query (OfType etc.) which checking for Invoice.
So you can check just for final classes, e.g.
bool isIndividualInvoice = await context.Invoices
.AnyAsync(e => e.Id == id && e is IndividualInvoice);
which btw is the same as
bool isIndividualInvoice = await context.IndividualInvoices
.AnyAsync(e => e.Id == id);
and similar (using Set<IndividualInvoice>() or Set<Invoice>().OfType<IndividualInvoice>).
Another not so clean option which works only for TPH is to retrieve the discriminator property value directly. You have to know its name and type (the defaults are "Discriminator" and string) and use the special EF.Property method similar to this:
var type = await context.Invoices
.Where(e => e.Id == id)
.Select(e => EF.Property<string>(e, "Discriminator")) // <--
.FirstOrDefaultAsync();
// here type will be ether null, "Invoice" or "IndividualInvoice"

Problem with mapping two objects (with lists)

I am looking for solution my issue... Probably my Shifts class cannot be mapped.
I have entity class Worker:
public class Worker
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(200)]
public string PhotoFilePath { get; set; }
public Workplace Workplace { get; set; }
public int WorkplaceId { get; set; }
public List<Service> Services { get; set; }
public List<Shift> Shifts { get; set; }
public IEnumerable<Worker> ToList()
{
throw new NotImplementedException();
}
}
And model WorkerModel:
public int Id { get; set; }
[Required]
[DisplayName("Imię")]
public string Name { get; set; }
[DisplayName("Nazwisko")]
public string LastName { get; set; }
[Display(Name = "Zdjęcie")]
public IFormFile Photo { get; set; }
public string PhotoFilePath { get; set; }
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int WorkplaceId { get; set; }
public List<ServiceModel> Services { get; set; }
public List<ShiftModel> Shifts { get; set; }
}
My default mapper profile:
//Mapping workers
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>();
And when I try map model to entity class in my action:
Worker worker = _mapper.Map<Worker>(model);
I get an issue:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
This is caused by different mapping types. Take the property Service as an example.
The resource is a type of Service.
But the destination is a type of ServiceModel.
So, they need to be converted. Here is a demo.
I create the Service and ServiceModel according to your model.
public class Service
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
public class ServiceModel
{
public int serviceID { get; set; }
public string myservice { get; set; }
}
This is mapping relationship.
public class AutomapProfile : Profile
{
public AutomapProfile()
{
CreateMap<Worker, WorkerModel>();
CreateMap<WorkerModel, Worker>()
.ForMember(m => m.Services, x => x.MapFrom(y => y.Services.Select(a=>
new Service
{
serviceID=a.serviceID,
myservice=a.myservice
})));
}
}
This is the mapping method.
public IActionResult Index()
{
var model = new WorkerModel
{
Id=1,
Name="names",
//...
Services = new List<ServiceModel>
{
new ServiceModel{ serviceID=1, myservice="service1"},
new ServiceModel{ serviceID=2, myservice="service2"},
},
//...
};
Worker worker = _mapper.Map<Worker>(model);
return Ok(worker);
}
Result.

map string filed to generic list in automapper based on .net core

I have a DomainModel and a DTO like this :
public class PostEntity: IEntity
{
[Required]
public string Description { get; set; }
public int Id { get; set; }
[Required]
public string Slug { get; set; }
[Required]
public string Tags { get; set; }
[Required]
public string Title { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public PostStatus Status { get; set; }
public User Writer { get; set; }
public int WriterId { get; set; }
}
public class PostDto
{
public int Id { get; set; }
public string Description { get; set; }
public string Slug { get; set; }
public string Tags { get; set; }
public string Title { get; set; }
public DateTime CreatedOn { get; }
public List<string> TagList { get; set; }
public PostDto()
{
TagList = new List<string>();
}
}
PostEntity'Tags contains some tags seperated by ",", now I want to split tags value by "," and convert it to List, to do this, I've tried this but I get the below compilation error
CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(",").ToList()));
I get this error :
An expression tree may not contain a call or invocation that uses optional arguments
I can't reproduce your error, it seems to work fine.
Below is an example where the TagList is correctly mapped
The code I used :
MapperConfiguration MapperConfiguration = new MapperConfiguration(configuration =>
{
configuration
.CreateMap<PostEntity, PostDto>().ForMember(dest => dest.TagList, cc => cc.MapFrom(src => src.Tags.Split(',').ToList()));
});
IMapper mapper = MapperConfiguration.CreateMapper();
PostEntity postEntity = new PostEntity
{
Tags = "Tag1,Tag2,Tag3,Tag4"
};
var mappedObject = mapper.Map<PostEntity, PostDto>(postEntity);
Please bear in mind that Expression.Call API does not support optional parameters. So, you should Replace Split(',') with
Split(',', System.StringSplitOptions.None)
or
Split(',', System.StringSplitOptions.RemoveEmptyEntries)
doing so you won't see that error again.

When including other entities other lists are loaded too which leads into a recursion, how can i prevent this?

I have a big database in the background storing:
public partial class Phone
{
public string Imei { get; set; }
public int ColourId { get; set; }
public int StorageId { get; set; }
public int TypeId { get; set; }
public int ModelId { get; set; }
public int PurchasePrice { get; set; }
public DateTime? SaleDate { get; set; }
public DateTime? RentalStart { get; set; }
public DateTime? RentalFinish { get; set; }
public virtual Colour Colour { get; set; }
public virtual Storage Storage { get; set; }
public virtual Type Type { get; set; }
}
public partial class Storage
{
public Storage()
{
Phone = new HashSet<Phone>();
}
public int StorageId { get; set; }
public string Amount { get; set; }
public virtual ICollection<Phone> Phone { get; set; }
}
And I am requesting for the Phone Data like this in my WebAPI:
[HttpGet]
// GET: Phones
public async Task<IActionResult> Index()
{
var phoneCalculatorContext = _context.Phone.Include(p =>
p.Colour).Include(p => p.Storage).Include(p => p.Type);
return Ok(await phoneCalculatorContext.Take(10).ToListAsync());
}
I have posted the JSON Response here:
https://textuploader.com/1dtu2
As you can see in my response is storage included (as expected) but storage has a reference to my Phone Collection and this goes on and on and on (like an recursion)
Is it possible to not get the lists?
Because I dont need the lists I just need to get the amount of storage but not the pone list.
Sorry for the poor understanding of EF but I am pretty new to this.
If you don't need that an storage know in how many phones it's, you can remove the ICollection property.
other solution is mark the phones property in storage as this
[JsonIgnore]
public virtual ICollection<Phone> Phone { get; set; }
For disabling self reference serialize, you could try SerializerSettings.ReferenceLoopHandling like
services.AddMvc()
.AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Can't correctly add associated objects into Entity Framework Context

I have and entity framework project exposed via a data service:
public class VersionContext : DbContext
{
public DbSet<VersionTreeEntry> VersionTreeEntries { get; set; }
public DbSet<PluginState> PluginStates { get; set; }
public static void SetForUpdates()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<VersionContext, Configuration>());
}
}
public class VersionTreeEntry
{
public VersionTreeEntry()
{
Children = new List<VersionTreeEntry>();
PluginStates = new List<PluginState>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public virtual ICollection<VersionTreeEntry> Children { get; set; }
public virtual ICollection<PluginState> PluginStates { get; set; }
public virtual VersionTreeEntry Ancestor { get; set; }
/// <summary>
/// Links to the ProtoBufDataItem Id for the session state.
/// </summary>
public int DataId { get; set; }
public string Notes { get; set; }
[Required]
public DateTime TimeStamp { get; set; }
[MinLength(1, ErrorMessage = "Tag cannot have a zero length")]
[MaxLength(20, ErrorMessage = "A tag name cannot contain over 20 characters")]
public string Tag { get; set; }
public bool IsUiNodeExpanded { get; set; }
[Required]
public string Version { get; set; }
[Required]
public string SessionName { get; set; }
}
public class PluginState
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string PluginName { get; set; }
[Required]
public byte[] Data { get; set; }
}
As far as I can see, the data classes are defined correctly. I try to create some new objects and add them into the context, with their relations intact:
var session = new Session();
session.SessionName = "My new session";
VersionTreeEntry versionTreeEntry = new VersionTreeEntry();
versionTreeEntry.SessionName = session.SessionName;
versionTreeEntry.Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
versionTreeEntry.TimeStamp = DateTime.Now;
_versionContext.AddToVersionTreeEntries(versionTreeEntry);
foreach (var plugin in session.Plugins)
{
using (var ms = new MemoryStream())
{
plugin.SaveState(ms);
PluginState state = new PluginState();
state.PluginName = plugin.PluginName;
state.Data = ms.ToArray();
versionTreeEntry.PluginStates.Add(state);
}
}
_versionContext.SaveChanges();
The problem is that the PluginState instances never actually get added to the database. If I add code to add them manually to the context, they do get added, but the foreign key pointing back to the VersionTreeEntry is null.
Again, this is a WCF DataService rather than vanilla EF, any idea what might be wrong?
Cheers
Posting the answer here from the comment section.
Agreed. The best way to do this is to call the following API:
_versionContext.AddRelatedObject(versionTreeEntry, "PluginStates", state);
Thanks
Pratik