OData complains about missing id property when enabling camelcasing - asp.net-web-api

I want to enable camel casing of my odata results. So I added EnableLowerCamelCase. But after I enabled that I get the following error message when I call:
http://localhost/odata/Users
The EDM instance of type '[Core.DomainModel.User Nullable=True]' is
missing the property 'id'.
Everything worked before I EnableLowerCamelCase, and it works again if I remove it. Also the error message is rather confusing. It says that User is missing the 'id' property. Which cannot be true. Because I have 'Id' defined as the key.
var builder = new ODataConventionModelBuilder();
builder.EnableLowerCamelCase();
var users = builder.EntitySet<User>(nameof(UsersController).Replace("Controller", string.Empty));
users.EntityType.HasKey(x => x.Id); // <--- id property
builder.GetEdmModel();
What am I doing wrong?

The way I solved this was to remove the entity key declaration from the EDM Model and to specifying it in the model itself
So my edm looks like `
var builder = new ODataConventionModelBuilder(serviceProvider);
builder.EnableLowerCamelCase();
var subscriptionSet = builder.EntitySet<SubscriptionDTO>("Subscriptions");
subscriptionSet.EntityType
.Filter() // Allow for the $filter Command
.Count() // Allow for the $count Command
.Expand() // Allow for the $expand Command
.OrderBy() // Allow for the $orderby Command
.Page() // Allow for the $top and $skip Commands
.Select(); // Allow for the $select Command
// subscriptionSet.EntityType.HasKey(s => s.Id);
//subscriptionSet.EntityType.EntityType.Property(s => s.Id).IsOptional();`
In the model use DataAnnotations to identify the key:
public class BaseModel
{
[Key]
public Guid? Id {get; set;}
public Guid? TenantId {get; set;}
public string Type {get; set;}
public bool Active {get; set;} = true;
public BaseModel() {
this.Id = System.Guid.NewGuid();
}
then use DTOs with Automapper as per convention:
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class BaseDTO
{
public Guid? Id {get; set;}
public Guid? TenantId {get; set;}
public string Type {get; set;}
public bool Active {get; set;} = true;
public BaseDTO() {
this.Id = System.Guid.NewGuid();
}
}
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class SubscriptionDTO: BaseDTO
{
[JsonProperty("email")]
public string Email {get; set;}
public SubscriptionDTO(): base() {
this.Type = "subscription";
}
}

Related

Automapper and EFCore Join

I have to tables I want to Join which I can do with just EFCore but I can't figure out how to also use automapper.
So if I have two Classes
public class ItemVM {
public int Id {get; set;}
public int ItemName { get; set; }
public int GroupId {get; set;}
}
public class GroupVM {
public int Id {get; set;}
public string GroupName {get; set;}
}
And I want to end up with:
public class ItemWithGroupVM {
public int Id {get; set;}
public string Name {get; set;}
public int GroupId {get; set;}
public string GroupName {get; set;}
}
So all our db fields have prefixes so in my mapping profile I have:
RecognizePrefixes("it");
CreateMap<Item, ItemVm>();
RecognizePrefixes("gr");
CreateMap<Group, GroupVm>();
Then I Run my queries:
var items = await _dbContext.Items.ProjectTo<ItemsVM>(_mapper.ConfigurationProvider).ToListAsync();
var groups = await _dbContext.Groups.ProjectTo<GroupsVM>(_mapper.ConfigurationProvider).ToListAsync();
Then I just loop through the list of items and generate a new list of ItemsWithGroupVM and set the group name string for the final output.
How can I modify my automapper profile and ef core query to do the join and the mapping without having to loop?

Entity Framework: M-to-n: Read a linked entity from a list

I know my title isn't quite specific and I'm sure my question has already been asked somewhere but I don't know how to search for it.
I'm using asp.net MVC with Entity framework and I have the following model:
public class Receipt
{
public int ReceiptId { get; set; }
public bool Deleted { get; set; }
public int UseraccountId { get; set; }
public int MessageId { get; set; }
public virtual Useraccount Useraccount { get; set; }
public virtual Message Message { get; set; }
}//end Receipt
A user has several Receipts.
I would like to get all messages from a specific user.
This is how I'm doing it so far:
//Get receipts of the logged in user
List<Receipt> receipts = UnitOfWork.ReceiptRepository.Get(
u => u.UseraccountId == loggedInUser.UseraccountId).ToList<Receipt>();
//Get all messages of the receipts
List<Message> messages = new List<Message>();
foreach (Receipt receipt in receipts)
{
messages.Add(receipt.Message);
}
Is there any better way?
Thanks in advance.
Do you have a link from Message to Receipt? I'm assuming from your Receipt class that it has a one-to-one relationship with your Message class. To be able to do this method, your Message class should look something like this:
public class Message
{
public int MessageId {get; set;}
//other properties here
//these provide the backward link to the Receipt that "owns" the message
public int ReceiptId {get; set;}
public virtual Receipt Receipt {get; set;}
}
Your model should recognize this link by having something like this (if you are using Code First)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Receipt>()
.HasOptional(r => r.Message)
.WithRequired(m => m.Receipt);
}
Your query should be done at the Repository level, specifically the Message repository because it will be the layer that has access to your database context. Your query could look something like this:
public IEnumerable<Message> GetAllMessagesByUserId (int userId)
{
return context.Set<Message>().Include(x => x.Receipt)
.Include (x => x.Receipt.Useraccount)
.Where (x => x.Receipt.Useraccount.UseraccountId == userId)
.ToList();
}
When you call UnitOfWork.MessageRepository.GetAllMessagesByUserId (loggedInUser.UseraccountId), it should return all messages that are linked to the user you are interested in.

Validating complex class in MVC

Consider the following code:
public class AccountNumber
{
[AccountNumber] //This validator confirms the format of the account number
public string Value {get; set;}
public int Format { get; set;}
public string ToString()
{
return Value + " is format " + Format;
}
}
public class MyViewModel
{
public MyViewModel()
{
SourceAccount = new AccountNumber();
DestinationAccount= new AccountNumber();
}
[Required]
AccountNumber SourceAccount {get; set;}
AccountNumber DestinationAccount {get; set;}
}
And then, in my View:
#Html.EditorFor(model => model.SourceAccount.Value)
#Html.EditorFor(model => model.DestinationAccount.Value)
Basically, I want to say that the user must enter a Source Account, and that they optionally enter a Destination Account. However, if they do enter a Destination Account it must conform to a certain format.
The problem with the code above is that the required validator on the SourceAccount will always return valid, as SourceAccount is never null. What would be a good approach for implementing what I am trying to achieve?
Please note that in real-life the setter for Value is more complex than shown, as it reformats the account number in a canonical format.
Edit Please note that we have to use inbuilt MVC validation, as that is what the rest of the project is currently using.
See Extending the Model Binder for Enhanced Validation.
This is fully compatible with built-in MVC validation.
You can - of course - customize this solution by using your own interface for validation.
A simple approach could be to add simple string properties for the SourceAccount and DestinationAccount numbers as follows:
public class MyViewModel
{
public MyViewModel()
{
}
[Required]
[AccountNumber]
public string SourceAccountNumber { get; set; }
[AccountNumber]
public string DestinationAccountNumber { get; set; }
public AccountNumber SourceAccount
{
get
{
return new AccountNumber
{
Value = SourceAccountNumber,
Format = 0 // Set Format appropriately
};
}
}
public AccountNumber DestinationAccount
{
get
{
return new AccountNumber
{
Value = DestinationAccountNumber,
Format = 0 // Set Format appropriately
};
}
}
}
Maybe you'd like to try FluentValidation, it's a model validation alternative to data annotation attributes, which allows you to add more complex model validation logic.
The code is still pretty concise and straightforward:
[Validator(typeof(PersonValidator))]
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(x => x.Id).NotNull();
RuleFor(x => x.Name).Length(0, 10);
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Age).InclusiveBetween(18, 60);
}
}

Working with interfaces and EF Fluent API

Code
I will show you the code and then explain the problem
Interfaces
public interface IUser
{
Guid ID { get; set; }
string Name { get; set; }
ICollection<IRole> Roles { get; set; }
}
public interface IRole
{
Guid ID { get; set; }
string Name { get; set; }
}
Notice that the interface IUser define a collection Roles of type IRole
Implementation
public class Role : IRole
{
public Guid ID { get; set; }
public string Name { get; set; }
}
public class User : IUser
{
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<IRole> Roles { get; set; }
}
EF Fluent API Configuration
public class RoleConfiguration : EntityTypeConfiguration<Role>
{
public RoleConfiguration()
{
HasKey(p => p.ID)
.Property(p => p.ID);
Property(p => p.Name)
.IsRequired()
.HasMaxLength(70);
}
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(p => p.ID)
.Property(p => p.ID)
.IsRequired();
Property(p => p.Name)
.HasMaxLength(60)
.IsRequired();
HasMany(r => r.Roles).WithMany();
}
}
Note that the configuration EntityTypeConfiguration where T is the implementation and not the interface (the EF does not allow to put the interface as T)
Problem
#1 situation:
If you run the application, to generate the relational model, the following error occurs:
The navigation property 'Roles' is not a declared property on type 'User'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The navigation property 'Roles' is not a declared property on type 'User'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Source Error:
Line 58: public IQueryable<Project> GetAll(int pageIndex, int pageSize, params Expression<Func<Project, object>>[] includeProperties)
Line 59: {
Line 60: return includeProperties.Aggregate<Expression<Func<Project, object>>,
Line 61: IQueryable<Project>>(Context.Projects, (current, includeProperty) => current.Include(includeProperty)).OrderBy(p => p.Name).Skip(pageIndex).Take(pageSize);
Line 62: }
#2 situation:
If you comment out the line HasMany(r => r.Roles).WithMany(); EF will generate the relational model with no relationship between User and Role (which should be many to many)
I believe this is because the User class, there is a collection type ICollection<IRole> and not of kind ICollection.
Question
The question is, how to solve this problem?
How to map the collection public ICollection<IRole> Roles { get; set; } using Fluent API EF
CodeFirst doesn't support mapping interfaces. You will need to change it to reference the Role concrete class.
Here's how I would implement your interfaces in order to work around the problem that Betty described.
public class Role : IRole
{
public Guid ID { get; set; }
public string Name { get; set; }
}
public class User : IUser
{
public Guid ID { get; set; }
public string Name { get; set; }
// Explicit implementation of the IUser.Roles property.
// Exists to satisfy the IUser interface but is not normally used.
// Used only when the client code types this object the interface, IUser.
// Otherwise ignored.
ICollection<IRole> IUser.Roles
{
get { return this.Roles as ICollection<IRole>; }
set { this.Roles = value as ICollection<Role>; }
}
// This property will be used in most cases.
// Used when the client code types this object as the concrete type, User.
public ICollection<Role> Roles { get; set; }
}

How to change default behavior of .Where method in EntityFramework 4.1?

I'd like to change the default behavior of .Where method for specific case(s).
All my Business Objects inherit from BaseObject that has property int ID {get; set;}
// Base class
public abstract class BaseObject
{
public abstract int ID {get; set;}
}
I have 2 classes:
public partial class User : BaseObject
{
public override int ID {get; set;}
public string Username { get; set; }
public int ProfileID { get; set; }
public virtual Profile Profile { get; set; }
}
public partial class Profile : BaseObject
{
public override int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
public static Profile GetAdminProfile()
{
return new Profile(){ID = 3, Name = "Admin profile"};
}
}
I would like to write
// This throws Unable to create a constant value of type 'Profile'... exception
User admin = Users.Where(user.Profile == Profile.GetAdminProfile()).FirstOrDefault();
instead of
User admin = Users.Where(user.Profile.ID == Profile.GetAdminProfile().ID).FirstOrDefault();
Is there a way to achieve this?
This is a known problem in Entity Framework. You will have follow the second approach.

Resources