Creating an index on shadow property - asp.net-core

I am implementing a model containing a number of owned types and need to create indexes on properties of the owned types. As I understand I cannot create the indexes directly in Fluent API using builder.HasIndex(entity => entity.location.geo); but instead I should use a Shadow Property and create the index on this. I think I have implemented the Shadow Property (Geo) correctly, but the indexer doesn't work. Please help.
I'm using .NET5
Here is my model (simplified for readability)
public class Meeting
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
public Location Location { get; set; }
}
public class Location
{
public Point Geo { get; set; }
}
And the configuration class:
public class MeetingConfiguration : IEntityTypeConfiguration<Meeting>
{
public void Configure(EntityTypeBuilder<Meeting> builder)
{
builder
.ToTable("Meetings")
.HasKey(c => c.Id);
builder
.Property(c => c.Id)
.HasColumnType("bigint")
.ValueGeneratedOnAdd()
.HasComment("Unique ID of the Meeting");
builder
.Property(c => c.Name)
.HasColumnType("varchar(127)")
.IsRequired()
.HasComment("Name of the Meeting");
builder
.Property(c => c.StartDate)
.HasColumnType("date")
.IsRequired()
.HasComment("First date of the Meeting");
// Configure Location owned entity
builder
.OwnsOne(p => p.Location, location =>
{
location
.Property(p => p.Name)
.HasColumnType("nvarchar(127)")
.IsRequired(false)
.HasComment("Optional: Name of the Location");
location
.Property(p => p.Geo)
.HasColumnType("geography (point)")
.IsRequired(false)
.HasComment("Optional: GPS Coordinates of the Location");
});
// Create Shadow properties for use in indexes
builder
.Property<Point>("Geo");
builder
.IndexerProperty<Point>("Geo");
// Create Indexes
builder
.HasIndex(entity => new
{
entity.Name,
entity.StartDate
});
}
}

Related

Automapper mapping to properties from a nested object [duplicate]

I am trying to map a result I get from database and I have the following models
public class ClientTest
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClientTestDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClientDbItem
{
public ClientTest Client { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Value { get; set; }
}
and the following mapping
CreateMap<ClientTest, ClientTestDto>();
CreateMap<ClientDbItem, ClientTestDto>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.Client));
When I run the software I get
Custom configuration for members is only supported for top-level
individual members on a type
Why is that happening If I am creating the config for ClientTest and ClientTestDto first?
There's a special API for that, IncludeMembers. See here.
This error might also arise when you try to map the top-level element to a constructor:
.ForMember(dest => dest, opt => opt.MapFrom(
src => new B1(src.Prop3, src.Prop4)));
Instead of mapping the top-level element MEMBER to the constructor (which works fine):
.ForMember(dest => dest.MyProperty, opt => opt.MapFrom(
src => new B1(src.Prop3, src.Prop4)));
To avoid this, use .ForCtorParam
Example:
CreateMap<Source, DestinationWithConstructor>()
.ForCtorParam("code", opt => opt.MapFrom(src => src.Prop3))
.ForCtorParam("text", opt => opt.MapFrom(src => src.Prop4))

Mapping Id to Model for controller API

I'm using asp.net core on a project. (I'm fairly new to it)
I have a User Model. the code below is a simplified version:
public class User
{
public int id { get; set; }
// attribute declaration
public ICollection<User> friends { get; set; }
}
I'm using automapper service to map my api to this Model:
public class UserResource
{
public UserResource()
{
this.friendsId = new List<int>();
}
public int id { get; set; }
// attribute declaration
public ICollection<int> friendsId { get; set; }
}
consider a post request to UserController with the following body:
{
"id" : 1
"friendsId": [2,3,4],
}
I want to map integers in friendsId to id of each user in friends collection. but I can't figure out what to do. here's what I've got:
CreateMap<UserResource,User>()
.ForMember(u => u.friends,opt => opt.MapFrom(????);
is this the right approach? if so how should I implement it?
or should I change my database model to this:
public class User
{
public int id { get; set; }
// attribute declaration
public ICollection<int> friendsId { get; set; }
}
Thank you in advance.
You'll need to implement a custom value resolver. These can be injected into, so you can access things like your context inside:
public class FriendsResolver : IValueResolver<UserResource, User, ICollection<User>>
{
private readonly ApplicationDbContext _context;
public FriendsResolver(ApplicationDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public ICollection<User> Resolve(UserResource source, User destination, ICollection<User> destMember, ResolutionContext context)
{
var existingFriendIds = destMember.Select(x => x.Id);
var newFriendIds = source.friendsId.Except(existingFriendIds);
var removedFriendIds = existingFriendIds.Except(source.Friends);
destMember.RemoveAll(x => removedFriendIds.Contains(x.Id);
destMember.AddRange(_context.Users.Where(x => newFriendIds.Contains(x.Id).ToList());
return destMember;
}
}
Not sure if that's going to actually work as-is, as I just threw it together here, but it should be enough to get your going. The general idea is that you inject whatever you need into the value resolver and then use that to create the actual stuff you need to return. In this case, that means querying your context for the User entities with those ids. Then, in your CreateMap:
.ForMember(dest => dest.friends, opts => opts.ResolveUsing<FriendsResolver>());
This only covers one side of the relationship, though, so if you need to map the other way, you may need a custom resolver for that path as well. Here, I don't think you actually do. You should be able to just get by with:
.ForMember(dest => dest.friendsId, opts => opts.MapFrom(src => src.friends.Select(x => x.Id));
This would help
CreateMap<UserResource,User>()
.ForMember(u => u.friends,opt => opt.MapFrom(t => new User {FriendsId = t.friendsId);
public class User
{
...
public ICollection<User> friends { get; set; }
}
Where friends is ICollection<User> whereas UserResource class has ICollection<int>. There is type mismatch here. You need to map ICollection to ICollection that is why I casted new User ...

Entity Framework 5 One to One relationship (e.g. User -> Profile) - ModelBuilder ASP.NET MVC4

I am trying to do a similar thing to what this previous answer had here:
How to declare one to one relationship using Entity Framework 4 Code First (POCO)
The problem is, im very new to this and am using Entity Framework 5 code first and the HasConstraint doesnt exist anymore, not to mention Im not good at lamda. I was wondering if anyone could help expand on this so I can map a User class to a Profile class effectively and easily? I need to know how to do this for the configuration files and model builder
Each user has one profile
Also, another quick question, say the profile model had Lists in this, how would I put these effectively in the model builder and configuration files?
Thank you
e.g.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public Profile Profile { get; set; }
// public int ProfileId { get; set; }
}
public class Profile
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PostalCode { get; set; }
}
// ...
modelBuilder.Entity<User>()
.HasOptional(x => x.Profile)
.WithRequired();
ProfileId is useless, FK is on the 'other side of the fence' (in Profile).
(this makes most sense IMO)
If you do need an Id in User (e.g. to be able to fill in Profile just by its ID when adding User - which if one-to-one is not really used - as you create both profile and user), then you can reverse...
modelBuilder.Entity<User>()
.HasRequired(x => x.Profile)
.WithOptional();
...and your ProfileId is actually in the Id (pk -> pk).
That solution worked for me
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.HasOne(d => d.Profile).WithOne(p => p.User);
});
modelBuilder.Entity<UserProfile>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.Property(e => e.Type)
.HasMaxLength(10)
.HasColumnType("nchar");
entity.HasOne(d => d.User).WithOne(p => p.Profile);
});
}

Multiple hasmany same keycolumn - illegal access to loading collection

it works. pull the data, but gives this error: illegal access to loading collection
public class Image : File
{
public virtual string ImagePath { get; set; }
}
public class Video : File
{
public virtual string VideoPath { get; set; }
public virtual string VideoType { get; set; }
}
public class Service : ContentBase
{
public virtual IList<Image> Images { get; set; }
public virtual IList<Video> Videos { get; set; }
}
public class ServiceMap:SubclassMap<Domain.Service>
{
public ServiceMap()
{
DiscriminatorValue("Service");
HasMany(x => x.Images).KeyColumn("ContentBase");
HasMany(x => x.Videos).KeyColumn("ContentBase");
}
}
public class ImageMap:SubclassMap<Image>
{
public ImageMap()
{
DiscriminatorValue("Image");
Map(x => x.ImagePath);
}
}
public class VideoMap:SubclassMap<Video>
{
public VideoMap()
{
DiscriminatorValue("Video");
Map(x => x.VideoPath);
}
}
it works. but it gives this error when I query. I think the same "keycolumn" gives this error to be. mapping'i How should I do?
Are you aware that joinalias wont eagerload the collections? that's what Fetch is for. Try this one instead
var service = UnitOfWork.CurrentSession.QueryOver<Service>()
.Fetch(x => x.Images).Eager
.Fetch(x => x.Videos).Eager
.Where(x => x.Id == serviceId)
.SingleOrDefault();
Update:
illegal access to loading collection could be thrown when
session is closed when accessing not initialized collection
mapping and collection type mismatch
Database doesn't match
have you inspected the sql generated to see if NH tries to access nonexistant columns or columns on the wrong table?

Fluent NHibernate one to many not saving children

I am using Fluent NHibernate. This is a classic case of a one to many relationship. I have one Supply parent with many SupplyAmount children.
The Supply parent object is saving with correct info, but the amounts are not getting inserted into the db when I save the parent. What am I doing for the cascade not to work?
The entities are as follows:
public class Supply : BaseEntity
{
public Guid SupplyId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Comments { get; set; }
public virtual IList<SupplyAmount> Amounts { get; set; }
public Supply()
{
Amounts = new List<SupplyAmount>();
}
public virtual void AddAmount(SupplyAmount amount)
{
amount.Supply = this;
Amounts.Add(amount);
}
}
public class SupplyAmount : BaseEntity
{
public virtual Guid SupplymountId { get; set; }
public virtual Supply Supply { get; set; }
public virtual int Amount { get; set; }
}
And the mapping as follows:
public class SupplyMap : ClassMap<Supply>
{
public SupplyMap()
{
Id(x => x.SupplyId);
Map(x => x.LastName);
Map(x => x.FirstName);
Map(x => x.Comments);
HasMany<SupplyAmount>(x => x.Amounts)
.Inverse().Cascade.SaveUpdate()
.KeyColumn("SupplyAmountId")
.AsBag();
}
}
public class SupplyAmountMap : ClassMap<SupplyAmount>
{
public SupplyAmountMap()
{
Id(x => x.SupplyAmountId);
References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Map(x => x.Amount);
}
}
And this is how I call it:
public SaveIt()
{
Supply sOrder = Supply();
sOrder.FirstName = "TestFirst";
sOrder.LastName = "TestLast";
sOrder.Comments = "TestComments";
for (int i = 0; i < 5; i++)
{
SupplyAmount amount = new SupplyAmount();
amount.Amount = 50;
amount.Supply = sOrder;
sOrder.AddAmount(amount);
}
// This call saves the Supply to the Supply table but none of the Amounts
// to the SupplyAmount table.
AddSupplyOrder(sOrder);
}
I know this is an old post but why not...
// This call saves the Supply to the Supply table but none of the Amounts
This comment in SaveIt() indicates you call the save on the Supply and not the amounts.
In this case you have your logic the wrong way around.
So to fix this:
SupplyMap -> The Inverse shouldn't be there for Amounts.
HasMany<SupplyAmount>(x => x.Amounts).Cascade.SaveUpdate();
SupplyAmountMap ->
remove References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Replace it with
References<Supply>(x=>x.Supply);
You should now be right to call the save on your supply object only and it will cascade down to the amounts.
Session.Save(supply);
In your test after you have arrange the supply and supplyamount make sure you call a
Session.Flush()
after your save to force it in.
This isn't as important in code as you will usually run in transactions before recalling the supply object.
Cheers,
Choco
Also as a side note it usually not a good idea to be to verbose with fluentmappings. let the default stuff do it thing which is why I would recommend against the column naming hints.