Mapping a single child - c#

The mapping below works, but I was wondering if it can be done with less configuration. I've tried playing around with ForAllMembers and ForSourceMember but I haven't found anything that works so far.
Classes
public class User
{
[Key]
public int ID { get; set; }
public string LoginName { get; set; }
public int Group { get; set; }
...
}
public class UserForAuthorisation
{
public string LoginName { get; set; }
public int Group { get; set; }
}
public class Session
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public virtual User User { get; set; }
...
}
Configuration
Mapper.CreateMap<Session, UserForAuthorisation>()
.ForMember(u => u.LoginName, m => m.MapFrom(s => s.User.LoginName))
.ForMember(u => u.Group, m => m.MapFrom(s => s.User.Group));
Query
UserForAuthorisation user = this.DbContext.Sessions
.Where(item =>
item.ID == SessionID
)
.Project().To<UserForAuthorisation>()
.Single();
Edit This works for the reverse.
Mapper.CreateMap<UserForAuthorisation, User>();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u));
var source = new UserForAuthorisation()
{
Group = 5,
LoginName = "foo"
};
var destination = Mapper.Map<Session>(source);
Unfortunately, Reverse() isn't the easy solution, mapping doesn't work.
Mapper.CreateMap<UserForAuthorisation, User>().ReverseMap();
Mapper.CreateMap<UserForAuthorisation, Session>()
.ForMember(s => s.User, m => m.MapFrom(u => u)).ReverseMap();
var source = new Session()
{
User = new User()
{
Group = 5,
LoginName = "foo"
}
};
var destination = Mapper.Map<UserForAuthorisation>(source);

I can see only one option to do less configurations. You can use benefit of flattering by renaming properties of UserForAuthorisation class to:
public class UserForAuthorisation
{
public string UserLoginName { get; set; }
public int UserGroup { get; set; }
}
In this case properties of nested User object will be mapped without any additional configuration:
Mapper.CreateMap<Session, UserForAuthorisation>();

Related

Linq with Lambda - how do I restrict joined table rows?

I want to use Linq to duplicate this T-SQL query on a sports teams database, to look up the experienced players in handball teams:
Select TE.TeamName, PL.FirstName, PL.LastName
From T_Team as TE
Inner Join T_Player As PL
On PL.Team_ID = TE.Team_ID
And PL.ExpLevel = 'Experienced'
Where TE.SportName = 'Handball'
I've tried creating two entities for my two tables:
public class TTeam
{
public int TeamId { get; set; }
public string TeamName { get; set; }
public string SportName { get; set; }
public virtual List<TPlayer> TeamPlayers { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TTeam> p_ebpTable)
{
p_etbTable.ToTable("T_TEAM");
p_etbTable.HasKey(t => new { t.TeamId }).HasName("PK_TEAMID_T_TEAM");
// Column definitions
// Foreign Keys
p_etbTable.HasMany(t => t.TeamPlayers).
WithOne(p => p.CurrentTeam).
HasPrincipalKey(t => t.TeamId).
HasForeignKey(p => p.TeamId);
}
}
and
public class TPlayer
{
public int PlayerId { get; set; }
public int TeamId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ExpLevel { get; set; }
public virtual TTeam CurrentTeam { get; set; }
// Called in the context OnModelCreating() method
public static void CreateModel(EntityTypeBuilder<TPlayer> p_ebpTable)
{
p_etbTable.ToTable("T_PLAYER");
p_etbTable.HasKey(t => new { t.PlayerId }).HasName("PK_PLAYERID_T_PLAYER");
// Column definitions
// Foreign Keys
p_etbTable.HasOne(p => p.CurrentTeam).
WithMany(t => t.TeamPlayers).
HasForeignKey(p => p.TeamId).
HasPrincipalKey(t => t.TeamId);
}
}
then use them in
using Microsoft.EntityFrameworkCore;
IEnumerable<TTeam> z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Include(te => te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced"));
but looping through z_enbHandballTeams in a foreach, throws an InvalidOperationException with the message "Lambda expression used inside Include is not valid".
(I guess it goes without saying that ExpLevel is a number and SportName is actually SportId, but I felt it would look easier to read that way.)
What am I doing wrong?
EF Core 3.1.x do not support filtered Include. Workaround is to do that via Select
var z_enbHandballTeams = z_dbcDbContext.TTeamRepository
.Where(te => te.SportName == "Handball")
.Select(te => new TTeam
{
TeamId = te.TeamId,
TeamName = te.TeamName,
SportName = te.SportName,
TeamPlayers = te.TeamPlayers.Where(pl => pl.ExpLevel == "Experienced")
.ToList()
});

How to create a table corresponding to enum in EF Core Code First?

How would one turn the enums used in an EF Core database context into lookup tables and add the relevant foreign keys?
Same as EF5 Code First Enums and Lookup Tables but for EF Core instead of EF 6
Related to How can I make EF Core database first use Enums?
You can use an enum in your code and have a lookup table in your db by using a combination of these two EF Core features:
Value Conversions - to convert the enum to int when reading/writing to db
Data Seeding - to add the enum values in the db, in a migration
Here below a data model example:
public class Wine
{
public int WineId { get; set; }
public string Name { get; set; }
public WineVariantId WineVariantId { get; set; }
public WineVariant WineVariant { get; set; }
}
public enum WineVariantId : int
{
Red = 0,
White = 1,
Rose = 2
}
public class WineVariant
{
public WineVariantId WineVariantId { get; set; }
public string Name { get; set; }
public List<Wine> Wines { get; set; }
}
Here the DbContext where you configure value conversions and data seeding:
public class WineContext : DbContext
{
public DbSet<Wine> Wines { get; set; }
public DbSet<WineVariant> WineVariants { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=wines.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Wine>()
.Property(e => e.WineVariantId)
.HasConversion<int>();
modelBuilder
.Entity<WineVariant>()
.Property(e => e.WineVariantId)
.HasConversion<int>();
modelBuilder
.Entity<WineVariant>().HasData(
Enum.GetValues(typeof(WineVariantId))
.Cast<WineVariantId>()
.Select(e => new WineVariant()
{
WineVariantId = e,
Name = e.ToString()
})
);
}
}
Then you can use the enum values in your code as follow:
db.Wines.Add(new Wine
{
Name = "Gutturnio",
WineVariantId = WineVariantId.Red,
});
db.Wines.Add(new Wine
{
Name = "Ortrugo",
WineVariantId = WineVariantId.White,
});
Here is what your db will contain:
I published the complete example as a gist: https://gist.github.com/paolofulgoni/825bef5cd6cd92c4f9bbf33f603af4ff
Here is another example :
public class Weather {
public int Id { get; init; }
public WeatherType Type { get; init; }
}
public enum WeatherType {
Cloudy = 1,
Sunny = 2,
Rainy = 3,
}
And you can add HasConversion in a seperate class like this :
public class WeatherEntityTypeConfiguration : IEntityTypeConfiguration<Weather>
{
public void Configure(EntityTypeBuilder<Weather> builder)
{
builder.ToTable("Weather").HasKey(k => k.Id);
builder.Property(p => p.Id).IsRequired();
builder.Property(p => p.Type).HasConversion<int>().IsRequired();
// builder.Property(p => p.Type).HasConversion<string>().IsRequired();
}
}
Note : If you use HasConversion<int>() data would be stored in database as an integer but if you use HasConversion<string>() data would be stored as string (in this example : Cloudy, Sunny or Rainy )
In addition to #PaoloFulgoni, here is how you'd do it if you want a many-to-many relationship with enums i.e. you want many user roles or wine variants and work with enums moreover, you can't store it as a flag because you need to know about the roles/privileges without source code(on db side).
TLDR ;) You'd have to create a join table which contains about about who has what privilege(or roles if you want).
There is a Users table which has a list of privileges, a privilege table which has privilege definition i.e. Id, name. And a Join table which will have User and Privilege as it's key. If an entry against this user/privilege combination is present that means this user has this privilege/role.
The code:
//for enum
public enum UserPrivilegeId : int
{
AddProject = 0,
ModifyProject = 1,
DeleteProject = 2,
AddUser = 3,
ModifyUser = 4,
DeleteUser = 5
}
//User class
public record User
{
public User()
{
Privileges = new HashSet<Privilege>();
}
public int Id { get; set; }
public string Username { get; set; }
public string PasswordHash { get; set; }
public virtual ICollection<Privilege> Privileges { get; set; }
public virtual List<UserPrivilege> UserPrivileges { get; set; }
}
//Privilege Class
public record Privilege //note record is IMPORTANT here, because this forces it to compare by value, if you want to *use a class*, then make sure to override GetHashCode and Equals
{
public Privilege()
{
Users = new HashSet<User>();
}
public Privilege(UserPrivilegeId privilegeId, string privilegeName)
{
PrivilegeId = privilegeId;
PrivilegeName = privilegeName;
Users = new HashSet<User>();
}
[Key]
public UserPrivilegeId PrivilegeId { get; set; }
public string PrivilegeName { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual List<UserPrivilege> UserPrivileges { get; set; }
}
//and finally the UserPrivilege join class
public record UserPrivilege
{
public UserPrivilegeId PrivilageId { get; set; }
public Privilege Privilage { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
//The set-up in dbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Privilege>()
.HasKey(p => p.PrivilegeId);
modelBuilder.Entity<Privilege>()
.Property(p => p.PrivilegeId)
.HasConversion<int>();
modelBuilder.Entity<User>()
.HasMany(user => user.Privileges)
.WithMany(privilege => privilege.Users)
.UsingEntity<UserPrivilege>(
j => j
.HasOne(up => up.Privilage)
.WithMany(u => u.UserPrivileges)
.HasForeignKey(up => up.PrivilageId),
j => j
.HasOne(up => up.User)
.WithMany(p => p.UserPrivileges)
.HasForeignKey(up => up.UserId),
j =>
{
j.Property(u => u.PrivilageId).HasConversion<int>();
j.HasKey(u => new { u.PrivilageId, u.UserId });
});
//this adds definitions of privileges to the table
modelBuilder.Entity<Privilege>()
.HasData(
Enum.GetValues(typeof(UserPrivilegeId))
.Cast<UserPrivilegeId>()
.Select(p => new Privilege(p, p.ToString())));
base.OnModelCreating(modelBuilder);
}
Use it by creating a wrapper around it with a boolean on IsActive like this:
public class UserPrivelegesDTO
{
public UserPrivelegesDTO(UserPrivilegeId privilege, bool isActive)
{
this.PrivilegeId = privilege;
this.PrivilegeName = privilege.ToString();
this.IsActive = isActive;
}
public UserPrivilegeId PrivilegeId { get; set; }
public string PrivilegeName { get; set; }
public bool IsActive { get; set; }
}
If you want to convert from List<Privileges> to List<UserPrivilegeDTO>, you can
return await _context.Privileges.OrderBy(x => x.PrivilegeId).ToListAsync(cancellationToken);
To Convert back to List<Privileges>, simply
var privileges = _userPrivilegesViewModel.Privileges.Where(x => x.IsActive).Select(x => new Privilege(x.PrivilegeId, x.PrivilegeName));
If you want to check if the user has privilege
var user = _context.Users.Include(x => x.Privileges).FirstAsync(x => x.Id == 1);
if (request.Editor.Privileges.Any(p => p.PrivilegeId == UserPrivilegeId.ModifyUser))
return true;
When you want to update privileges
var PrivilegeChangeUser = await
_context.Users
.Include(user => user.Privileges)
.Include(user => user.UserPrivileges)
.FirstOrDefaultAsync(user => user.Id == request.UserId);
//**NOTE**you *need* to include the join table i.e. UserPrivileges in order replace the privileges, if you do not include it EF will try to add the privileges which already exist :(
//To update the privileges from an IEnumerable<UserPrivilegeIdEnum>
//first get the privileges objects and add that to users
var AllPrivileges =
await _context.Privileges
.Include(x => x.UserPrivileges)
.Include(x => x.Users)
.Where(x =>
request.Privileges
.Contains(x.PrivilegeId)
).ToListAsync(cancellationToken);
PrivilegeChangeUser.Privileges = AllPrivileges;

AutoMapper Ignore the Mapping Conditionally when the List type of property in ViewModel is null

I have the view model as below:
public class CarViewModel
{
public Guid Id { get; set; }
public string CarName { get; set; }
public List<EngineTypeViewModel> Engine { get; set; }
}
public class EngineTypeViewModel
{
public int Id { get; set; }
public string name { get; set; }
}
Entity:
public class Car
{
public Guid Id { get; set; }
public string CarName { get; set; }
public virtual ICollection<EngineType> Engine { get; set; }
}
public class EngineType
{
public int Id { get; set; }
public string name { get; set; }
}
I wanted to Map the CarViewModel to Car Entity types as below:
CreateMap<CarViewModel, Car>()
.ForMember(dest => dest.Engine, src => src.Ignore());
In the mapping configuration I wanted to map from CarViewModel to Car.
But if the engine count is 0 then the mapped should be ignore.
CreateMap<CarViewModel, Car>()
.ForMember(dest => dest.Engine, src => src.Ignore());
//Map only when src.Engine.Count > 0 other wise Ignore
// What should be my approach here??
This means during the update when i pull using GetById. Like
public Update(CarViewModel model)
{
var car = obj.GetById(model.Id);
var mapped = map.Map(model,car);
//if the model.Engine.Count = 0 car.Engine
//should be same as mapped.Engine, there should not be mapping with the
//object(Engine) from the `CarViewModel`
}
Is there any extension in Auto mapper to this task. I have tried as above src.Ignore() it is going to ignore during the ADD also.
So, I do not want to MAP the null value during the Update. Or, it should remain as the destination value
Edit:
AutoMapper Configuration:
CreateMap<CarViewModel, Car>().ForMember(dest => dest.Engine, o =>
{
o.Condition(src => src.Engine.Count > 0);
//mapping
o.MapFrom(src => src.Engine);
});
Mapping Records:
var carmodel = new CarViewModel();
carmodel.CarName = "Maruti";
carmodel.Id = Guid.NewGuid();
var carentity = new Car();
carentity.CarName = "Maruti";
carentity.Id = Guid.NewGuid();
carentity.Engine.Add(new EngineType { Id = 1, name = "PetrolEngine" });
carentity.Engine.Add(new EngineType { Id = 2, name = "DieselEngine" });
carentity.Engine.Add(new EngineType { Id = 3, name = "CranotEngine" });
var mapped = _mapper.Map(carmodel, carentity);
You can look at the mapped. mapped.Engine is Empty
Expected Output:

Query nested class and return the all root document in MongoDB via C# driver 2.1

I have a collection called 'Projects'.
In every Project I have subDocuments called Structures that included another subDocuments called StructureProperties.
I wish to get the Property by the 'userId' and also it's ROOT using the C# driver 2.1 (userId - found in every Properties subDocuments).
my Project class look like that:
public interface IGeneralProject
{
[BsonId]
ObjectId Id { get; set; }
string Name { get; set; }
List<GeneralStructure> GeneralStructures { get; set; }
}
[BsonKnownTypes(typeof(ProjectOne), typeof(ProjectTwo))]
public class GeneralProject : IGeneralProject
{
[BsonId]
public ObjectId Id { get; set; }
public string Name { get; set; }
public List<GeneralStructure> GeneralStructures { get; set; }
}
Structure class:
public interface IGeneralStrucute
{
ObjectId StructureId { get; set; }
string Name { get; set; }
List<GeneralStructureProperty> StructureProperties { get; set; }
}
[BsonKnownTypes(typeof(Structure), typeof(Houseware))]
public class GeneralStructure : IGeneralStrucute
{
public ObjectId StructureId { get; set; }
public string Name { get; set; }
public List<GeneralStructureProperty> StructureProperties { get; set; }
}
and the last one StructureProperty:
public interface IGeneralStructureProperty
{
string Name { get; set; }
ObjectId UserId { get; set; }
}
[BsonKnownTypes(typeof(Propery), typeof(Office))]
public class GeneralStructureProperty
{
public string Name { get; set; }
public ObjectId UserId { get; set; }
}
I have tried LINQ to query that but got stuck...
here some of my attempts:
1.
return (from project in projectCollection.AsQueryable()
from generalStructure in project.GeneralStructures
from generalStructureProperty in generalStructure.StructureProperties
where generalStructureProperty.UserId == ObjectId.Parse(userId)
select new GeneralProject()
{
Id = project.Id, Name = project.Name, GeneralStructures = new List<GeneralStructure>()
{
new GeneralStructure()
{
StructureId = generalStructure.StructureId, Name = generalStructure.Name, StructureProperties = new List<GeneralStructureProperty>()
{
new GeneralStructureProperty()
{
Name = generalStructureProperty.Name, UserId = generalStructureProperty.UserId
}
}
}
}
}).ToList();
2.
var query = from project in projectCollection.AsQueryable()
from structure in project.GeneralStructures
from structureProperty in structure.StructureProperties
where structureProperty.UserId == ObjectId.Parse(userId)
select // where I got stuck...
3.
var query =
projectCollection.AsQueryable()
.Select(project => project)
.Where(
project =>
project.GeneralStructures.Any(
structure =>
structure.StructureProperties.Any(
property => property.UserId == ObjectId.Parse(userId)))).ToList();
It very important to say that I could make that query and get good results, but I have been got the all Project document with *all of is subDocuments** instead of getting the Project with the specific Structure and the specific StructurePropery ('ROOT' of the node).
Do you want to return only the root document GeneralProject (as you mentioned in the subject)? You can try this:
var collection = mongoContext.GetCollection<GeneralProject>(); //your implementation here
var result = await collection
.Find(x => x.GeneralStructures.Any(y => y.StructureProperties.Any(z => z.UserId == {user id here})))
.FirstOrDefaultAsync();
Or if you want to return GeneralProject and single GeneralStructureProperty with user id, you can try project it dynamically:
var result = await collection
.Find(x => x.GeneralStructures.Any(y => y.StructureProperties.Any(z => z.UserId == {user id here})))
.Project(
p => new
{
GeneralStructureProperty = p.GeneralStructures.FirstOrDefault(x => x.StructureProperties.Any(z => z.UserId == {user id here})),
GeneralProject = p
})
.FirstOrDefaultAsync();
I didn't test it on real database and I suppose, these operations could be complicated for large amounts of data in db. But it based on your db architecture.
EDIT:
According to your comments:
var result = await collection
.Find(x => x.GeneralStructures.Any(y => y.StructureProperties.Any(z => z.UserId == ObjectId.Parse(userId))))
.Project(
p => new
{
Id = p.Id,
Name = p.Name,
Structure = p.GeneralStructures.FirstOrDefault(x => x.StructureProperties.Any(z => z.UserId == ObjectId.Parse(userId)))
})
.FirstOrDefaultAsync();
result.Structure.StructureProperties = result.Structure.StructureProperties.Where(x => x.UserId == ObjectId.Parse(userId)).ToList();
Now you should retrieve project id, project name and structure with properties (with expected userId). Adjust my suggestion to your needs.

Compare object with an array with another array

I have a model Group:
public class GroupModel
{
[Key]
public int GroupModelId { get; set; }
[Required]
[MaxLength(50)]
[DataType(DataType.Text)]
public string GroupName { get; set; }
[Required]
public virtual ICollection<FocusArea> FocusAreas { get; set; }
...
And a model Focus:
public class FocusArea
{
public int FocusAreaId { get; set; }
public FocusEnum Focus { get; set; }
public List<ApplicationUser> ApplicationUser { get; set; }
public virtual ICollection<GroupModel> GroupModel { get; set; }
public enum FocusEnum
{
Psych,
Medical,
LivingWith
}
Group and Focus has a many-to-many relationship. My Controller is receiving:
public ActionResult GroupSearch(string[] focusSelected) // Values possible are Pysch, Medical and LivingWith
{
List<GroupModel> groups;
...
Problem: I want to select the groups that have all the focus that are inside the focusSelected array.
What I've tried:
groups = groups.Where(t => t.FocusAreas.Where(x => focusSelected.Contains(x.Focus))).ToList()).ToList();
Obviously not working. Does anyone have another idea?
This may help you
var result = groups.Where(g => g.FocusAreas.All(f => focusSelected
.Any(fs => (FocusEnum)Enum.Parse(typeof(FocusEnum), fs, true) == f.Focus)));
Where needs a delegate / expression that returns bool. In your sample - you are putting Where inside Where, where Where returns collection.
Changing inner Where to All should do the trick:
var allSelParsed = focusSelected.Select(s => (FocusEnum)Enum.Parse(typeof(FocusEnum), s)
.ToList();
groups = groups.Where(gr => allSelParsed.All(selected =>
gr.FocusAreas.Any(fc =>
fc.Focus == selected)))
.ToList();
This should give you expected result
var result = groups.Where(g =>
focusSelected.All(fs =>
g.FocusAreas.Any(fa => fa.ToString() == fs)));

Categories

Resources