Asp.Net core ODATA compute property after filter - asp.net-core

Is there any way to create a computed property on an ODATA HTTP request, after the ODATA filter has been processed?
I have the following model
class Person
{
public string Name { get; set; }
public string Email { get; set; }
public string ImageLink { get; set; } //This is computed
}
And the following IEdmModel
private static IEdmModel GetEdmModel(IApplicationBuilder app)
{
var builder = new ODataConventionModelBuilder(app.ApplicationServices).EnableLowerCamelCase();
var person = builder.EntitySet<Person>("People").EntityType;
person.Property(x => x.Name);
person.Property(x => x.Email);
person.Property(x => x.ImageLink).IsOptional();
return builder.GetEdmModel();
}
The image link is generated dynamically and is a fairly costly process, as the link is signed. Is there a way to add only add this property when it is explicitly included by a $select and to only calculate after the filter is applied?
Setting the Select(SelectExpandType.Allowed) property does not seem to exclude it from the model.
I've managed to manually exclude the property by checking the query string in the controller. But the property is still included in the model, the value is null, and obviously this runs before any filter has applied.
var people = await _personService.FetchAllPeopleAsync(cancellationToken);
if (Request.Query.TryGetValue("$select", out var values) &&
values.Any(v => v.Contains(nameof(Person.ImageLink))))
{
return _photoService.AttachImageLinks(Url, people);
}
return people;

Related

ef core context set from property type

On my project I am doing soft delete (changing to false isActive property and filtering records using EF Core query filter ) for records and I need a way to delete (also soft delete) navigation properties (which can be a reference property or a collection) that I put a attribute to them.
This is the Attribute I wrote for property :
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
public class DatabaseAttribute : Attribute
{
bool softDelete;
public virtual bool SoftDelete
{
get { return softDelete; }
set { softDelete = value; }
}
}
It used :
public Guid? CargoTransactionId { get; set; }
[Database(SoftDelete = true)]
public CargoTransaction CargoTransaction { get; set; }
The Code at below I can find navigation property which I have flagged for soft delete :
var entries = Context.ChangeTracker.Entries();
foreach (var entry in entries)
{
var entryNavigations = entry.Navigations;
foreach (var navigation in entryNavigations)
{
var dbAttribute = navigation.Metadata.PropertyInfo.CustomAttributes
.Where(p => p.AttributeType == typeof(DatabaseAttribute))
.FirstOrDefault();
}
}
bu I could not able to find DbSet entity type to use in context like with the method DbContext.Set<TEntity>().
Can you help with that?

ASP.NET Core OData entity with private setters on properties

I have this entity:
public int Id { get; private set; }
public string Name { get; private set; }
public Behavior Behavior { get; private set; }
public Product(int id, string name, Behavior behavior)
{
Id = id;
Name = name;
Behavior = behavior;
}
In startup method I'm registering the EdmModel :
var builder = new ODataConventionModelBuilder();
var entitySet = builder.EntitySet<Product>("Products");
entitySet.EntityType.HasKey(x => x.Id);
var model = builder.GetEdmModel();
app.UseMvc(route =>
{
route.Select().Filter().Expand().OrderBy().Count().MaxTop(null);
route.MapODataServiceRoute("odata", null, model);
route.EnableDependencyInjection();
}
);
When I'm running my app, this exception occurs:
InvalidOperationException: The entity 'Product' does not have a key
defined.
If I change private setter to public all is working. Also others properties with private setters are giving: ODataException Product does not contain property with name 'Name'. How can I solve it ?
the question is quite old, I stumbled across the same issue now. Scalar properties (i.e. int, string, bool) with private setters are not recognized by the ODataConventionModelBuilder, even though it recognizes collections with private setters.
I could solve the problem using EntityTypeConfiguration<T> obtained via the model builder:
public class Article
{
public string ArticleNr { get; private set; }
public string SomeProperty { get; private set; }
}
var builder = new ODataConventionModelBuilder();
var articleBuilder = builder.EntityType<Article>();
articleBuilder.HasKey(a => a.ArticleNr);
articleBuilder.Property(a => a.SomeProperty);
builder.EntitySet<Article>("Articles");
var model = builder.GetEdmModel();
This is giving me a model that can be built, it recognizes the key in spite of its private setter and I can also issue queries against SomeProperty. But this way every property must be registered explicitly using a call to Property which seems very error prone when adding new properties. I think it should be able to write a custom convention for it, but I have not tried this so far.

Initializing referenced objects in entity framework unit of work

I have a class in Entity framework 5 (using MVC 4):
public class JobFunction
{
public int Id { get; set; }
public string JobFunctionName { get; set; }
public int StatusId { get; set; }
public Status JFStatus { get; set; }
}
In my OnModelCreating method, I establish a FK relationship with the Status table as follows:
modelBuilder.Entity<JobFunction>().HasRequired(a => a.JFStatus).
WithMany().HasForeignKey(u => u.StatusId).WillCascadeOnDelete(false);
In my controller, I get a list of JobFunction objects as follows:
List<JobFunction> jfList = uow.JobFunctionRepository.GetAll().ToList<Catalog>();
where uow is my Unit of Work object, and JobFunctionRepository is defined. When I examine any JobFunction object in jfList, I see the following in my watch window:
Id: 1
JfStatus: null
JobFunctionName: "Manager"
StatusId: 2
Note that JFStatus is null. My question is: what provisions do I make in my code to initialize JFStatus to the appropriate Status object (based on the value of StatusId), during my GetAll call?
Thanks in advance.
-Zawar
You need some instrument to apply eager loading when you load the data through your repository. For example you could give your GetAll method a parameter list of expressions for the navigation properties you want to include in your query:
using System.Data.Entity;
//...
public IQueryable<JobFunction> GetAll(
params Expression<Func<JobFunction, object>>[] includes)
{
IQueryable<JobFunction> query = context.JobFunctions;
foreach (var include in includes)
query = query.Include(include);
return query;
}
Then you call it like so:
List<JobFunction> jfList = uow.JobFunctionRepository
.GetAll(jf => jf.JFStatus)
.ToList();
The JFStatus property should be filled now.

Can I cache entities with non mapped properties in NHibernates 2nd level cache?

Hi i have setup my SessionFactory to cache entities and queries:
private ISessionFactory CreateSessionFactory()
{
var cfg = new Configuration().Proxy(
properties => properties.ProxyFactoryFactory<DefaultProxyFactoryFactory>()).DataBaseIntegration(
properties =>
{
properties.Driver<SqlClientDriver>();
properties.ConnectionStringName = this.namedConnection;
properties.Dialect<MsSql2005Dialect>();
}).AddAssembly(this.resourceAssembly).Cache(
properties =>
{
properties.UseQueryCache = true;
properties.Provider<SysCacheProvider>();
properties.DefaultExpiration = 3600;
});
cfg.AddMapping(this.DomainMapping);
new SchemaUpdate(cfg).Execute(true, true);
return cfg.BuildSessionFactory();
}
This is my user mapping
public class UserMapping : EntityMapping<Guid, User>
{
public UserMapping()
{
this.Table("USERS");
this.Property(
x => x.CorpId,
mapper => mapper.Column(
c =>
{
c.Name("CorporateId");
c.UniqueKey("UKUserCorporateId");
c.NotNullable(true);
}));
this.Set(
x => x.Desks,
mapper =>
{
mapper.Table("DESKS2USERS");
mapper.Key(km => km.Column("UserId"));
mapper.Inverse(false);
mapper.Cascade(Cascade.All | Cascade.DeleteOrphans | Cascade.Remove);
},
rel => rel.ManyToMany(mapper => mapper.Column("DeskId")));
this.Cache(
mapper =>
{
mapper.Usage(CacheUsage.ReadWrite);
mapper.Include(CacheInclude.All);
});
}
}
What I want to do is get a user or query some users and add information to the domain object and cache the updated object.
public class User : Entity<Guid>, IUser
{
public virtual string CorpId { get; set; }
public virtual ISet<Desk> Desks { get; set; }
public virtual MailAddress EmailAddress { get; set; }
public virtual string Name
{
get
{
return string.Format(CultureInfo.CurrentCulture, "{0}, {1}", this.SurName, this.GivenName);
}
}
public virtual string GivenName { get; set; }
public virtual string SurName { get; set; }
}
something like this:
var users = this.session.Query<User>().Cacheable().ToList();
if (users.Any(user => user.EmailAddress == null))
{
UserEditor.UpdateThroughActiveDirectoryData(users);
}
return this.View(new UserViewModel { Users = users.OrderBy(entity => entity.Name) });
or this:
var user = this.session.Get<User>(id);
if (user.EmailAddress == null)
{
UserEditor.UpdateThroughActiveDirectoryData(user);
}
return this.View(user);
The UpdateThroughActiveDirectory methods work but are executed everytime i get data from the cache, the updated entities do not keep the additional data. Is there a way to also store this data in nhibernates 2nd level cache?
NHibernate doesn't cache entire entity in second level cache. It caches only the state / data from the mapped properties. You can read more about it here: http://ayende.com/blog/3112/nhibernate-and-the-second-level-cache-tips
There's an interesting discussion in comments of that post that explains this a little further:
Frans Bouma: Objects need to serializable, are they not? As we're talking about multiple appdomains. I wonder what's more
efficient: relying on the cache of the db server or transporting
objects back/forth using serialization layers.
Ayende Rahien: No, they don't need that. This is because NHibernate doesn't save the entity in the cache. Doing so would open
you to race conditions. NHibernate saves the entity data alone,
which is usually composed of primitive data (that is what the DB can
store, after all). In general, it is more efficient to hit a cache
server, because those are very easily scalable to high degrees, and
there is no I/O involved.

AutoMapper Update Actions in ASP.NET MVC

This is probably quite straight forward for some, however I'm a bit confused and can't find a decent example. Say I'm using view models and my POST action takes in that view model. Typically I would do something along the following lines:
[HttpPost]
public ActionResult Update(UserViewModel uvm)
{
User user = Mapper.Map<UserViewModel, User>(uvm);
_repository.Update(user);
return RedirectToAction("Index");
}
Although this isn't the full picture. The mapping would work fine, however if I were to just update what I've mapped then it'd get rid of valuable data in the database because of course in this case I'm not updating the password or other details.
My repository looks something like this:
public void Update(User user)
{
User u = Session.QueryOver<User>().Where(x => x.UserName == user.UserName).SingleOrDefault();
if (u == null)
throw new Exception("User not found");
u.Forename = user.Forename;
u.Surname = user.Surname;
u.EmailAddress = user.EmailAddress;
}
[I'm using NHibernate so it'll save the object back to the DB once the session is closed (after the request has finished) automatically for me.]
So my question is, in my repository should I load the "User" entity, then update the values I want, and then save it back, or is there another method to do this? The reason I ask is because it seems a bit... "manual" if you see what I mean? Perhaps it is correct, but I just wanted to see opinions of those with more experience in this area.
Cheers
I use the following approach:
[HttpPost]
public ActionResult Update(UserViewModel uvm)
{
User user = _userRepository.FindById(uvm.Id);
user.Forename = uvm.Forename;
user.Surname = uvm.Surname;
user.EmailAddress = uvm.EmailAddress;
_userRepository.Update(user);
return RedirectToAction("Index");
}
UPDATE:
To address the comments about AutoMapper here's how to proceed:
Let's take for example the following classes:
public class UserViewModel
{
public string Forename { get; set; }
public string Surname { get; set; }
public string EmailAddress { get; set; }
}
public class User
{
public string Forename { get; set; }
public string Surname { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
}
We don't want to modify the user password in the UI. So we express our intention to AutoMapper:
Mapper
.CreateMap<UserViewModel, User>()
.ForMember(dest => dest.Password, opt => opt.Ignore());
and then:
[HttpPost]
public ActionResult Update(UserViewModel uvm)
{
// Fetch the original model we would like to update
User user = _userRepository.FindById(uvm.Id);
Mapper.Map(uvm, user);
// At this stage the user model will have its
// Forename, Surname and EmailAddress properties
// updated from the view model and its Password property
// will remain the one we got from the repository
_userRepository.Update(user);
return RedirectToAction("Index");
}
UPDATE 2:
To address the question in the comments about configuring AutoMapper I usually use Profiles:
public class UsersProfile : Profile
{
protected override void Configure()
{
Mapper
.CreateMap<UserViewModel, User>()
.ForMember(dest => dest.Password, opt => opt.Ignore());
Mapper
.CreateMap<User, UserViewModel>();
}
}
and then have a registry class which registers all the mappers:
public class MappingsRegistry
{
public static void Configure()
{
Mapper.AddProfile(new UsersProfile());
Mapper.AddProfile(new SomeOtherProfile());
...
}
}
which is called in Application_Start:
MappingsRegistry.Configure();
Finally my controllers have a reference to the mapping engine:
public class UsersController : Controller
{
private readonly IUsersRepository _repository;
private readonly IMappingEngine _mappingEngine;
public ContratsFCController(IUsersRepository repository, IMappingEngine mapperEngine)
{
_repository = repository;
_mapperEngine = mapperEngine;
}
[AutoMap(typeof(User), typeof(UserViewModel))]
public ActionResult Update(int id)
{
var user = _repository.FindById(id);
return View(user);
}
[HttpPost]
public ActionResult Update(UserViewModel uvm)
{
if (!ModelState.IsValid)
{
return View(uvm);
}
var user = _repository.FindById(uvm.Id);
_mapperEngine.Map(uvm, user);
_repository.Update(user);
return RedirectToAction("Index");
}
}
Now all that's left is to instruct your DI framework to pass the Mapper.Engine property to the constructor and in your unit tests obviously substitute them with an appropriate mock.