ABP AppService for hierarchical data - hierarchical-data

I need an AppService to load a treeview with a recursive collection of entities like these:
===Products===
Id
Description
Price
Products[] ====> Id
Description
Price
Products[]
====> Id
Description
Price
Products[] ====> Id
Description
Price
Products[]
Is there a ready-made class to derive from? If not, could you suggest what class to derive or what interface to implement, and how to proceed, please?
PS: Possibly with full CRUD operations, but the most important is understand how to load the data.

_productRepository
.GetAllIncluding(x=>x.Products)
.Where(p => p.Id == 1); // or whatever condition
.ToList()
this will give you the list of products and child products of the product with Id 1.

You can use the Include method to specify related data to be included in query results.
Please refer Loading Related Data.
You can refer the below sample.
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace EFQuerying.RelatedData
{
public class Sample
{
public static void Run()
{
#region SingleInclude
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ToList();
}
#endregion
#region IgnoredInclude
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.Select(blog => new
{
Id = blog.BlogId,
Url = blog.Url
})
.ToList();
}
#endregion
#region MultipleIncludes
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.Include(blog => blog.Owner)
.ToList();
}
#endregion
#region SingleThenInclude
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
#endregion
#region MultipleThenIncludes
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.ToList();
}
#endregion
#region MultipleLeafIncludes
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.Include(blog => blog.Posts)
.ThenInclude(post => post.Tags)
.ToList();
}
#endregion
#region IncludeTree
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ThenInclude(author => author.Photo)
.Include(blog => blog.Owner)
.ThenInclude(owner => owner.Photo)
.ToList();
}
#endregion
#region Eager
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
context.Entry(blog)
.Collection(b => b.Posts)
.Load();
context.Entry(blog)
.Reference(b => b.Owner)
.Load();
}
#endregion
#region NavQueryAggregate
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
var postCount = context.Entry(blog)
.Collection(b => b.Posts)
.Query()
.Count();
}
#endregion
#region NavQueryFiltered
using (var context = new BloggingContext())
{
var blog = context.Blogs
.Single(b => b.BlogId == 1);
var goodPosts = context.Entry(blog)
.Collection(b => b.Posts)
.Query()
.Where(p => p.Rating > 3)
.ToList();
}
#endregion
}
}
}

Related

Moq MongoDB UpdateOneAsync method for unit testing

I want to moq update method that is using mongodbContext. here is what i am trying to do but its not working. how to pass UpdateResult return type .ReturnsAsync<UpdateResult>(). I am very new to Mongodb database Unit Testing with .net core.
public void UpdateEventAsync_Test()
{
//Arrange
var eventRepository = EventRepository();
var pEvent = new PlanEvent
{
ID = "testEvent",
WorkOrderID = "WorkOrderID",
IsDeleted = false,
IsActive = true,
EquipmentID = "EquipmentID"
};
////Act
mockEventContext.Setup(s => s.PlanEvent.UpdateOneAsync(It.IsAny<FilterDefinition<PlanEvent>>(), It.IsAny<UpdateDefinition<Model.EventDataModel.PlanEvent>>(), It.IsAny<UpdateOptions>(), It.IsAny<System.Threading.CancellationToken>())).ReturnsAsync<UpdateResult>();
var result = eventRepository.UpdateEventAsync(pEvent);
////Assert
result.Should().NotBeNull();
Assert.AreEqual(true, result);
}
below is the code for which i want to write Moq Test
public async Task<bool> UpdateEventAsync(Model.EventDataModel.PlanEvent eventobj)
{
var filter = Builders<Model.EventDataModel.PlanEvent>.Filter.Where(f => f.ID == eventobj.ID);
// TO Do: Use replace instead of update.
var updatestatement = Builders<Model.EventDataModel.PlanEvent>.Update.Set(s => s.IsDeleted, eventobj.IsDeleted)
.Set(s => s.EquipmentID, eventobj.EquipmentID)
.Set(s => s.PlannedStartDateTime, eventobj.PlannedStartDateTime)
.Set(s => s.PlannedEndDatetime, eventobj.PlannedEndDatetime)
.Set(s => s.WorkOrderID, eventobj.WorkOrderID)
.Set(s => s.ResourceID, eventobj.ResourceID)
.Set(s => s.LastUpdatedBy, eventobj.LastUpdatedBy)
.Set(s => s.EventComment, eventobj.EventComment)
.Set(s => s.SiteID, eventobj.SiteID)
.Set(s => s.LastUpdatedDateTime, DateTime.UtcNow.ToString());
UpdateResult updateResult = await _eventContext.PlanEvent.UpdateOneAsync(filter, updatestatement);
return updateResult != null && updateResult.IsAcknowledged && updateResult.ModifiedCount > 0;
}
Either create an instance or mock UpdateResult and return that from the setup
public async Task UpdateEventAsync_Test() {
//...omitted for brevity
var mockUpdateResult = new Mock<UpdateResult>();
//Set up the mocks behavior
mockUpdateResult.Setup(_ => _.IsAcknowledged).Returns(true);
mockUpdateResult.Setup(_ => _.ModifiedCount).Returns(1);
mockEventContext
.Setup(_ => _.PlanEvent.UpdateOneAsync(It.IsAny<FilterDefinition<PlanEvent>>(), It.IsAny<UpdateDefinition<Model.EventDataModel.PlanEvent>>(), It.IsAny<UpdateOptions>(), It.IsAny<System.Threading.CancellationToken>()))
.ReturnsAsync(mockUpdateResult.Object);
//Act
var result = await eventRepository.UpdateEventAsync(pEvent);
//Assert
result.Should().Be(true);
}
Also note that the test needs to be made async to be exercised accurately.

Query Performance on Many To Many With EF6

We're running into a performance issue with a query that utilizes a few M:M relationships with a single table.
Tables:
Currently, EF generates a MONSTROUS query that uses UNION ALL to the Contact table 3 times. Pastebin of SQL Once for each join. Our projection is like so
var contacts = query.Select(contact => new Contact
{
Brands = contact.Brands.Select(b => new Brand
{
Id = b.Id,
Name = b.Name,
Aliases = b.Aliases.Select(a => a.Value).ToList()
}).ToList(),
Categories = contact.Categories.Select(category => new Category
{
Id = category.Id
}).ToList(),
ContactTypes = contact.ContactTypes.Select(ct => new ContactType
{
Id = ct.Id
}).ToList(),
Disabled = contact.Disabled,
Email = contact.Email,
Fax = contact.Fax,
Id = contact.Id,
Latitude = contact.Coordinates != null ? contact.Coordinates.Latitude : 0,
Longitude = contact.Coordinates != null ? contact.Coordinates.Longitude : 0,
Distance = searchLocation != null && contact.Coordinates != null ? searchLocation.Distance(contact.Coordinates) * conversionFactor : 0,
PostalCode = contact.PostalCode,
PhoneSupport = contact.PhoneSupport,
PhoneSales = contact.PhoneSales,
PhoneAfterHoursSupport = contact.PhoneAfterHoursSupport,
Phone = contact.Phone,
Preferred = contact.Preferred,
ShopOnlineImageUrl = contact.ShopOnlineImageUrl,
ContactTranslations = contact.NameContent.TranslatedContents.Where(tc => tc.IsoLanguageCode == searchModel.IsoLanguageCode ||
tc.IsoLanguageCode == SearchContactsHelpers.ISO_LANGUAGE_CODE_ENGLISH)
.Select(tc => new ContactTranslationImpl
{
Name = tc.Value,
IsoLanguageCode = tc.IsoLanguageCode,
NameId = tc.MasterContentId
}).ToList(),
AdditionalInformationContentId = contact.AdditionalInformationContentId,
AddressLine1ContentId = contact.AddressLine1ContentId,
AddressLine2ContentId = contact.AddressLine2ContentId,
CityContentId = contact.CityContentId,
StateOrProvinceContentId = contact.StateOrProvinceContentId,
CountryContentId = contact.Country.NameContentId,
HoursOfOperationContentId = contact.HoursOfOperationContentId,
WebsiteContentId = contact.WebsiteContentId,
OnlineSellerHomePageUrlContentId = contact.OnlineSellerHomePageUrlContentId,
ContactFormUrlContentId = contact.ContactFormUrlContentId,
RequestAQuoteUrlContentId = contact.RequestAQuoteUrlContentId,
NameContentId = contact.NameContentId
});
Removing the M:M relationships from the query produces a sane single query (albeit with many subselects). As soon as a single M:M relationship is added to the projection, EF produces 2 queries with a UNION ALL for the results.
Here's our configuration as well:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<Contact>()
.HasMany(c => c.Brands)
.WithMany(c => c.Contacts)
.Map(mc =>
{
mc.MapRightKey("BrandId");
mc.MapLeftKey("ContactId");
mc.ToTable("ContactBrands");
});
modelBuilder.Entity<Contact>()
.HasMany(c => c.Categories)
.WithMany(c => c.Contacts)
.Map(mc =>
{
mc.MapRightKey("CategoryId");
mc.MapLeftKey("ContactId");
mc.ToTable("ContactCategories");
});
modelBuilder.Entity<Contact>()
.HasMany(c => c.ContactTypes)
.WithMany(c => c.Contacts)
.Map(mc =>
{
mc.MapRightKey("ContactTypeId");
mc.MapLeftKey("ContactId");
mc.ToTable("ContactToContactType");
});
}
I am unsure of why EF is generating such a terrible query. Any insight on how to possibly improve the query is immensely helpful.

How to simplify transforming entity framework entities to DTOs

I map to a data transformation object when retrieving items from an ASP.NET Web API like so for a list:
public async Task<IList<PromotionDTO>> GetPromotionsList()
{
return await _context.Promotions
.Select(p => new PromotionDTO
{
PromotionId = p.PromotionId,
Is_Active = p.Is_Active,
Created = p.Created,
Title = p.Title,
BusinessName = p.BusinessName,
})
.Where(x => x.Is_Active)
.OrderByDescending(x => x.Created)
.ToListAsync();
}
And like this for getting a single record:
public async Task<PromotionDTO> GetPromotion(int id)
{
return await _context.Promotions
.Select(p => new PromotionDTO
{
PromotionId = p.PromotionId,
Is_Active = p.Is_Active,
Created = p.Created,
Title = p.Title,
BusinessName = p.BusinessName,
})
.Where(x => x.Is_Active && x.PromotionId == id)
.FirstOrDefaultAsync();
}
I'm new to DTO's and I find that I'm using the same DTO transformation code at many places, and was wondering how I can simplify my code to only do this once?
Though it may be enough to map like you've stated, but when your project starts to grow it will just complicated things and cause additional work.
I suggest that you use some kind of mapping library like AutoMapper.
https://github.com/AutoMapper/AutoMapper
static MyRepositoryConstructor()
{
// Define your maps
Mapper.Initialize(cfg => {
cfg.CreateMap<PromotionEntity, PromotionDTO>();
});
}
public async Task<IList<PromotionDTO>> GetPromotionsList()
{
return Mapper.Map<IList<PromotionDTO>>(await _context.Promotions
.Where(x => x.Is_Active)
.OrderByDescending(x => x.Created)
.ToListAsync()
);
}
public async Task<PromotionDTO> GetPromotion(int id)
{
return Mapper.Map<PromotionDTO>(await _context.Promotions
.Where(x => x.Is_Active && x.PromotionId == id)
.FirstOrDefaultAsync()
);
}
One option is to create a method which returns an IQueryable and then use that in each
Private IQueryable<PromotionDTO> Query()
{
return _context.Promotions
.Select(p => new PromotionDTO
{
PromotionId = p.PromotionId,
Is_Active = p.Is_Active,
Created = p.Created,
Title = p.Title,
BusinessName = p.BusinessName,
});
}
public async Task<IList<PromotionDTO>> GetPromotionsList()
{
return await Query()
.Where(x => x.Is_Active)
.OrderByDescending(x => x.Created)
.ToListAsync();
}
public async Task<PromotionDTO> GetPromotion(int id)
{
return await Query()
.Where(x => x.Is_Active && x.PromotionId == id)
.FirstOrDefaultAsync();
}

Register multiple dbcontext using autofac

We have application in asp.net MVC with DDD architecture with autofac IOC container. We are trying to register two dbcontext with different database connect. But the only last one is came in to effect. We are using Entity Framework 4.4.0. Here is the code.
var masterDataSettingManager = new SaasDataSettingManager();
if (masterDataSettingManager.LoadSettings() != null)
{
var masterProviderSettings = masterDataSettingManager.LoadSettings();
builder.Register(c => masterDataSettingManager.LoadSettings()).As<DataSettings>();
builder.Register(x => new EfDataProviderManager(x.Resolve<DataSettings>())).As<BaseDataProviderManager>().InstancePerDependency();
builder.Register(x => (IEfDataProvider)x.Resolve<BaseDataProviderManager>().LoadDataProvider()).As<IDataProvider>().InstancePerDependency();
builder.Register(x => (IEfDataProvider)x.Resolve<BaseDataProviderManager>().LoadDataProvider()).As<IEfDataProvider>().InstancePerDependency();
if (masterDataSettingManager != null && masterProviderSettings.IsValid())
{
var efDataProviderManager = new EfDataProviderManager(masterDataSettingManager.LoadSettings());
var dataProvider = (IEfDataProvider)efDataProviderManager.LoadDataProvider();
dataProvider.InitConnectionFactory();
var dbProviderFactory = efDataProviderManager.LoadDbProviderFactories();
builder.Register<IDbContext>(c => new MyDbContext1(masterProviderSettings.DataConnectionString, dbProviderFactory)).InstancePerHttpRequest();
}
else
{
builder.Register<IDbContext>(c => new MyDbContext1(masterDataSettingManager.LoadSettings().DataConnectionString)).InstancePerHttpRequest();
}
}
//data layer
var dataSettingsManager = new DataSettingsManager();
var dataProviderSettings = dataSettingsManager.LoadSettings();
builder.Register(c => dataSettingsManager.LoadSettings()).As<DataSettings>();
builder.Register(x => new EfDataProviderManager(x.Resolve<DataSettings>())).As<BaseDataProviderManager>().InstancePerDependency();
builder.Register(x => (IEfDataProvider)x.Resolve<BaseDataProviderManager>().LoadDataProvider()).As<IDataProvider>().InstancePerDependency();
builder.Register(x => (IEfDataProvider)x.Resolve<BaseDataProviderManager>().LoadDataProvider()).As<IEfDataProvider>().InstancePerDependency();
if (dataProviderSettings != null && dataProviderSettings.IsValid())
{
var efDataProviderManager = new EfDataProviderManager(dataSettingsManager.LoadSettings());
var dataProvider = (IEfDataProvider)efDataProviderManager.LoadDataProvider();
dataProvider.InitConnectionFactory();
var dbProviderFactory = efDataProviderManager.LoadDbProviderFactories();
builder.Register<IDbContext>(c => new MyDbContext2(dataProviderSettings.DataConnectionString, dbProviderFactory)).InstancePerHttpRequest();
}
else
{
builder.Register<IDbContext>(c => new MyDbContext2(dataSettingsManager.LoadSettings().DataConnectionString)).InstancePerHttpRequest();
}
builder.RegisterGeneric(typeof(EfRepository<>)).As(typeof(IRepository<>)).InstancePerHttpRequest();
Register them like this:
builder
.Register(c =>
new MyDbContext1(dataProviderSettings.DataConnectionString, dbProviderFactory))
.Named<IDbContext>("dbContext1").InstancePerHttpRequest();
builder
.Register(c =>
new MyDbContext2(dataProviderSettings.DataConnectionString, dbProviderFactory))
.Named<IDbContext>("dbContext2").InstancePerHttpRequest();
And then resolve them:
IDbContext dbContext = ctx.ResolveNamed<IDbContext>("dbContext1");
If you would like to inject it in a constructor you may do it like this:
builder
.RegisterType<SomeService>()
.WithParameter(
(p, c) => p.Name == "dbContext",
(p, c) => c.ResolveNamed<IDbContext>("dbContext1")
);
Apart from having named registrations pf dbContexts you still can have unnamed defined in parallel with them - it may be treated as default one. Iy then in your application you would like rather to use a named registration then you specify this desire in the above way.

Not able to access list of record in EF

Here is the problem I am trying to access some records from the data base based on one field . The field I am using is audit_id having type GUID .
but the line does not returning any data
var audits = ctx.Audits.Where(x => lstAudits.Contains(x.audit_id)).ToList();
Here is a my full code to update mass records in the database using EF
//will select auditId from the List
var lstAudits = _ViewModel.WorkingListAudits.Where(x => x.WorkingList).Select(x=>x.AuditId).ToList();
using (var ctx = new AuditEntities())
{
var audits = ctx.Audits.Where(x => lstAudits.Contains(x.audit_id)).ToList();
audits.ForEach(x => x.working_list = false);
ctx.SaveChanges();
}
In case of single record it return data from database
var lstAudits = _ViewModel.WorkingListAudits.Where(x => x.WorkingList).Select(x => x.AuditId).ToList();
Guid tempAuditId = lstAudits[0];
// lstAudits.ForEach(x => x.ToString().ToUpper());
using (var ctx = new AuditEntities())
{
var audits = (from au in ctx.Audits
where au.audit_id == tempAuditId
select au).ToList();
//foreach(Audit audit in audits){
//}
audits[0].working_list = false;
ctx.SaveChanges();
}
Finally I got the answer here is the updated code which is working fine .I just took some intermediate result in a temporary variable and it started working as expected
//will select auditId from the List
var lstAudits = _ViewModel.WorkingListAudits.Where(x => x.WorkingList).Select(x => x.AuditId).ToList();
using (var ctx = new AuditEntities())
{
var tempAudits = ctx.Audits.ToList();
var audits = tempAudits.Where(x => lstAudits.Contains(x.audit_id)).ToList();
audits.ForEach(x => x.working_list = false);
ctx.SaveChanges();
}