Generic function given showing exception - c#

I wrote following repository function in the repository and it is not showing the data.
public async Task<IEnumerable<User>> GetAllUser()
{
return await FindByCondition(ur => ur.IsDeleted != true).Include(x => x.UserRole.RoleType).ToListAsync();
}
Generic function for this is:
public IQueryable<TEntity> FindByCondition(Expression<Func<TEntity, bool>> expression)
{
return _mBHDbContext.Set<TEntity>().Where(expression).AsNoTracking();
}
It shows exception when writing the above code:
This error comes when "include" is using with the query. Means when we need data from two tables the problem showing.
And my entity model structure is look like:
public partial class User
{
public User()
{
PatientAnswers = new HashSet<PatientAnswer>();
}
public long UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool? IsDeleted { get; set; }
public int? UserRoleId { get; set; }
public DateTime? DataOfBirth { get; set; }
public virtual RoleMaster UserRole { get; set; }
public virtual ICollection<PatientAnswer> PatientAnswers { get; set; }
}
and other table structure is look like:
public partial class RoleMaster
{
public RoleMaster()
{
Users = new HashSet<User>();
}
public int Id { get; set; }
public string RoleType { get; set; }
public virtual ICollection<User> Users { get; set; }
}

Try using the below. This brings the information inside linked UserRole entity and avoids a round trip when accessing RoleType
public async Task<IEnumerable<User>> GetAllUser()
{
return await FindByCondition(ur => ur.IsDeleted != true).Include(x => x.UserRole).ToListAsync();
}

You can use ThenInclude like this:
public async Task<IEnumerable<User>> GetAllUser()
{
return await FindByCondition(ur => ur.IsDeleted != true)
.OrderBy(ro => ro.UserId)
.Include(x => x.UserRoleNavigation)
.ThenInclude(ur => ur.RoleType)
.ToListAsync();
}

It complains about Include(x => x.UserRole.RoleType) expression.
Include expect lambda poiting navigation property to include, so as I see it it's x.UserRole.
Moreover it does not make sense to Inlucde value property, such as string. If you want to access it (fetch it from db), it's enought to include navigation property that contains it, so after all, you need only Include(x => x.UserRole)

Related

C# and MongoDB - Returning values from an object

I'm hoping someone can help:
I have MongoDB collection for a User which has an Array called Reports which holds objects with IDs. I can retrieve the IDs but I'm trying to retrieve the lookup values from another collection, so the User.Reports.Id should return the values associated with that ID in the Reports collection. This would be something similar to the .populate function in Mongoose.
I have tried a number of solutions but don't seem to be able to get it working. From my research, it seems I should be using aggregate().lookup() but I haven't managed to get it working.
public class UserModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }
//from azure active directory b2c
public string? ObjectIdentifier { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? DisplayName { get; set; }
public string? EmailAddress { get; set; }
public string? PhoneNumber { get; set; }
public List<BasicReportsModel> Reports { get; set; } = new();
}
public class BasicReportsModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }
public BasicReportsModel()
{
}
public BasicReportsModel(ReportsModel report)
{
Id = report.Id;
}
}
private readonly IMongoCollection<UserModel> _users;
private readonly IMongoCollection<ReportsModel> _reports;
public MongoUserData(IDbConnection db)
{
_users = db.UserCollection;
_reports = db.ReportsCollection;
}
public async Task<UserModel> GetUserData(string id)
{
// this brings back the user model with the Reports array and objects. I need to bring back the documents related to the ID's in User.Reports.Id
var results = await _users.FindAsync(u => u.Id == id);
return results.FirstOrDefault();
}
Please could someone help me find a solution or point me in the right direction.
With aggregate and $lookup you are on the right track. In order to get this working with the C# driver, you should define a model class that contains the reports, e.g.:
public class UserWithReportsModel : UserModel
{
public IEnumerable<ReportsModel> Reports { get; set; }
}
You can then change your GetUserData method to carry out an aggregation with a $lookup stage:
public async Task<UserWithReportsModel> GetUserData(string id)
{
return await (_users.Aggregate()
.Match(x => x.Id == 1)
.Lookup(foreignCollection: _reports,
localField: x => x.ReportIds,
foreignField: x => x.Id,
#as: (UserWithReportsModel x) => x.Reports))
.FirstOrDefaultAsync();
}
The above statement returns all the data of the user with the details on the reports.

Load navigation properties in generic repository

I have a problem when using AutoMapper and EF Core together to map navigation properties from the model to the DTO. My EF classes are:
public class Meal
{
public DateTime Day { get; set; }
public MealType MealType { get; set; }
public int MealId { get; set; }
}
public class MealType
{
public string Name { get; set; }
public int MealTypeId { get; set; }
}
And the corresponding DTO classes:
public class ExistingMealDto
{
public DateTime Day { get; set; }
public ExistingMealTypeDto MealType { get; set; }
public int MealId { get; set; }
public string MealTypeName { get; set; }
}
public class ExistingMealTypeDto
{
public string Name { get; set; }
public int MealTypeId { get; set; }
}
This is my AutoMapper mapping:
config.CreateMap<DataLayer.EfClasses.MealType, ExistingMealTypeDto>();
config.CreateMap<DataLayer.EfClasses.Meal, ExistingMealDto>()
.ForMember(x => x.MealType, x => x.MapFrom(x=>x.MealType))
.ForMember(x => x.MealTypeName, x => x.MapFrom(y => y.MealType.Name));
I'm loading the data within a generic method that looks like this:
public IEnumerable<TDtoOut> GetAllAsDto<TIn, TDtoOut>()
where TIn : class
{
var allEntities = DbContext.Set<TIn>();
return Mapper.Map<IEnumerable<TDtoOut>>(allEntities);
}
When calling this code, all Meal instances are loaded from the database and MealId and Day are filled correctly. However, MealType is null and therefore ExistingMealDto.MealType is null as well. I can work around this problem by explicitly calling DbContext.MealTypes.ToList(), but since the method should be generic for TIn, this is not a production solution.
How can I solve this issue? Thanks!
For getting the related data in generic method , you can judge the Type of the passed type. The following is a test demo , you could refer to:
public IEnumerable<TIn> GetAllAsDto<TIn>()
where TIn : class
{
Type typeParameterType = typeof(TIn);
if (typeParameterType == typeof(User))
{
var Entities = _context.Set<User>().Include(u=>u.Orders);
return (IEnumerable<TIn>)Entities;
}
else
{
var allEntities = _context.Set<TIn>();
return allEntities;
}
}
public void Test()
{
var data = GetAllAsDto<User>();
var data1 = GetAllAsDto<Status>();
}
Result

Automapper says Missing map from System.String to System.Char? But I see no Char property

I've got three classes:
public class UserReport : Entity
{
public string Name { get; set; }
public string Email { get; set; }
public List<string> Departments { get; set; }
public List<string> Titles { get; set; }
}
public abstract class Entity
{
public Guid Id { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateLastModified { get; set; }
public string CreatedBy { get; set; }
public string LastModifiedBy { get; set; }
public bool Active { get; set; }
protected Entity()
{
DateCreated = DateTime.Now;
DateLastModified = DateTime.Now;
Id = Guid.NewGuid();
}
}
public class UserModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Departments { get; set; }
public string Titles { get; set; }
}
With my automapper configs set as:
CreateMap<List<string>, string>().ConvertUsing(strings => {
if (strings != null)
return string.Join("\n", strings);
return "";
});
CreateMap<UserReport, UserModel>();
When trying to call from a generic method using the Automapper Ef Extensions:
IQueryable<TModel> modelQueryable = _reportService.GetReportBaseQuery().ProjectToQueryable<TModel>();
I get this error
Missing map from System.String to System.Char. Create using Mapper.CreateMap.
GetReportBaseQuery() returns an IQueryable<TReport>, so the UserReport in this instance. I don't see any char properties, why is this coming up?
Just for testing I tried to make one:
CreateMap<String, Char>().ConvertUsing(x => x.FirstOrDefault());
And then it says:
Argument types do not match
Further research shows that this works:
Mapper.Map<List<TReport>, List<TModel>>(_reportService.GetReportBaseQuery().ToList());
But I can't use that since I need it to be a queryable returned. So something is different when I try to do an EF projection, not sure what that is though. Writing a select statement from one to the other is easy enough, but that's not generic and re-usable.
The solution is to explicitly set mapping for the Departments and Titles fields:
CreateMap<UserReport, UserModel>()
.ForMember(x => x.Departments, o => o.MapFrom(s => string.Join("\n", s.Departments)))
.ForMember(x => x.Titles, o => o.MapFrom(s => string.Join("\n", s.Titles)));
But, this does not explain, why this situation occurs.
As DOTang pointed out:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.Collections.Generic.IEnumerable1[System.String])' method, and this method cannot be translated into a store expression.
The AutoMapper extension seems to try to map the following thing:
IEnumerable<string> => IEnumerable<char>

Change state for property by string

Im trying to give a list of propertynames to my entity framework update method and these propertynames should be marked as unchanged. However It does get the properties, but than it gives the following error when trying to change the state:
The entity type RuntimePropertyInfo is not part of the model for the current context.
Which makes sense but Is there a way to get the object property that is on the current context?
Here is my code:
if (propertiesToSkip != null)
{
foreach (var propertyName in propertiesToSkip)
{
var prop = entity.GetType().GetProperty(propertyName);
_context.Entry(prop).State = EntityState.Unchanged;
}
}
I hope anybody can help.
Edit
public static void MigrateIqrAccountDetails(ref IUnitOfWork uow, Account account,
IEnumerable<List<string>> iqrContacts)
{
if (account.IqrAccount == null) throw new ArgumentException("Cannot migrate Account");
// Addresses
var addresses = ContactMigrationHelper.MigrateIqrContactAddresses(contact);
if (addresses != null)
account.Addresses.AddRange(addresses.Except(account.Addresses, new Comparers.AddressComparer()));
uow.Repository<IqrAccount>().Update(account.IqrAccount);
uow.Repository<Account>().Update(account, new List<string> {"Name"});
}
Here is the method that edits the object and should update it on the context.
The account is being gathered like this:
var accountToUpdate =
uow.Repository<Account>()
.GetAll(a => a.IqrAccount != null, "PhoneNumbers", "EmailAddresses", "Addresses")
.OrderBy(a => a.IqrAccount.UpdatedAt)
.FirstOrDefault();
In the repository that looks like this:
public IEnumerable<T> GetAll(Func<T, bool> predicate = null, params string[] includes)
{
var query = Include(_objectSet, includes);
return predicate != null ? query.Where(predicate).Where(o => !o.IsRemoved) : query.Where(o => !o.IsRemoved);
}
protected virtual IQueryable<T> Include(IQueryable<T> query, params string[] includes)
{
if (includes == null) return query;
query = includes.Aggregate(query, (current, item) => current.Include(item));
return query;
}
and the error im getting is:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Addresses_dbo.Accounts_AccountId". The conflict occurred in database "Test", table "dbo.Accounts", column 'Id'.
The statement has been terminated.
When savechanges is called.
Also here are my models:
public class Account : BaseEntity
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual List<Address> Addresses { get; set; }
public int? CommentCollectionId { get; set; }
public virtual LabelCollection LabelCollection { get; set; }
public virtual User AccountManager { get; set; }
[ForeignKey("ParentAccount")]
public virtual int? ParentAccountId { get; set; }
public virtual Account ParentAccount { get; set; }
public virtual ICollection<Account> SubAccounts { get; set; }
}
public class Address : BaseEntity
{
public int Id { get; set; }
public string Country { get; set; }
public string Region { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string Number { get; set; }
public string NumberExtension { get; set; }
public string ZipCode { get; set; }
public bool IsActive { get; set; }
public string Premise { get; set; }
public string SubPremise { get; set; }
public Location Coordinates { get; set; }
[ForeignKey("Account")]
public int AccountId { get; set; }
public Account Account { get; set; }
}
I guesse the problem is that the account is passed as a seperate object parameter and so it is no longer on the context? What is the good way of doing this?
Allright so I have fixed the issue now by removing the Account foreign key from the Address entity because the relation is also described on the Account entity. Although it does work now I still do not fully understand why it didn't when the foreign key was present on the entity. If anyone knows the answer I would still like to know.

How to eagerly load all referenced entities dynamically in Entity Framework

EF 6
I have the following POCO's
_
public class StructureEntity : EmEntityBase
{
[ForeignKey("ParentStructureId")]
public virtual StructureEntity ParentStructure { get; set; }
public long? ParentStructureId { get; set; }
[ForeignKey("SiteId")]
public virtual SiteEntity Site { get; set; }
[Required(ErrorMessage = "Required.")]
public long SiteId { get; set; }
[Required(ErrorMessage = "Required.")]
public string Name { get; set; }
}
public class SiteEntity : EmEntityBase
{
[ForeignKey("ParentSiteId")]
public virtual SiteEntity ParentSite { get; set; }
public long? ParentSiteId { get; set; }
[Required(ErrorMessage = "Required.")]
public long ClientId { get; set; }
[ForeignKey("ClientId")]
public ClientEntity Client { get; set; }
[Required(ErrorMessage = "Required.")]
public string Name { get; set; }
}
public class ClientEntity : EmEntityBase
{
public long? ParentClientId { get; set; }
[ForeignKey("ParentClientId")]
public virtual ClientEntity ParentClient { get; set; }
public string Name { get; set; }
}
Now when I want to eagerly load all the referenced entities. To do this I have:
public IQueryable<StructureDivisionEntity> GetAllWithInclude()
{
return GetAll()
.Include(e => e.Structure)
.Include(e => e.ParentStructureDivision.Structure)
.Include(e => e.Structure.Site.Client);
}
I was wondering if there was a dynamic way to do this without explicitly having to do the .Include(Structure) etc. Something along the lines of:
MyEntity.IncludeAllReferenced() where IncludeAllReferenced used reflection or similair to traverse MyEntity and do all the includes?
UPDATE: Alternative Approach To Querying Complex Graphs
http://www.codeproject.com/Articles/247254/Improving-Entity-Framework-Query-Performance-Using
https://entityframework.codeplex.com/workitem/1386
https://github.com/oinuar/GBQ
You can't do that, but you can make an extensionmethod to get all.
This might be a crazy idea, but you could make a generic extension-method to handle all your types, to make the code more clean.
Example, (one could extend this with factory-pattern if necessary):
public static IQueryable<T> IncludeAll<T>(this IQueryable<T> query)
{
if (typeof(T) == typeof(StructureDivisionEntity))
{
return GetAllWithInclude(query);
}
throw new NotImplementedException("IncludeAll not implemented for type {0}",typeof(T).FullName);
}
private static IQueryable<StructureDivisionEntity> GetAllWithInclude<StructureDivisionEntity> (IQueryable<StructureDivisionEntity> query)
{
return query.Include(e => e.Structure)
.Include(e => e.ParentStructureDivision.Structure)
.Include(e => e.Structure.Site.Client);
}

Categories

Resources