Include only one property, not entire database row - c#

Model:
public class Word
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime? WhenCreated { get; set; }
public ApplicationUser Author { get; set; }
[NotMapped]
public string AuthorName
{
get
{
if (Author != null)
{
return Author.UserName;
}
else {
return "";
}
}
}
public List<Definition> Definitions { get; set; }
}
Controller:
[HttpGet]
public IEnumerable<Word> Get()
{
return _db.Words.Include(x=>x.Author).ToList();
}
My Controller now returns entire ApplicationUser class which is one of properties of Word. I want to send only one property of ApplicationUser: UserName. How can I do that?
I've added AuthorName, which would return only data that I want from ApplicationUser. Unfortunately I still have to .Include(x=>x.Author) to make this property work. Can I somehow omit including Author in process of data serialization (to hide it when sending data to user)?
I know I can use .Select() method, but it requires me to type all properties I will need. If I modify my Model in the future, I will need to update all those .Select() which will would be inconvenient and waste of time.
How would you solve that?

You need to create a Dto object and assign the values to it and return the Dto instead.
Dto
public class WordDto
{
public string Title { get; set; }
public DateTime? WhenCreated { get; set; }
public string AuthorName { get; set; }
}
Then in your action
[HttpGet]
public async Task<IEnumerable<WordDto>> Get()
{
return _db.Words
.Include(x=>x.Author)
.Select(x =>
new WordDto
{
Title = x.Title,
DateTime = x.WhenCreated,
AuthorName = x.Author?.UserName ?? string.Empty
}
)
.ToListAsync();
}

You can try it as shown below.
Note : You don't need to use Include here.
[HttpGet]
public async Task<IEnumerable<Word>> Get()
{
return _db.Words.Select(x => new
{
Word = x,
AuthorName = x.Author.UserName
}
).ToList();
}

Create a View model and use AutoMapper to populate. Look at using AutoMapper and ProjectTo extension https://github.com/AutoMapper/AutoMapper/wiki/Queryable-Extensions
That way if you add properties to View model they will be automatically mapped if they exist on your EF model
So create a VM with required properties named appropriately (see AutoMapper docs on naming conventions):
public class WordVM
{
public string Title { get; set; }
public DateTime? WhenCreated { get; set; }
public string AuthorUserName { get; set; }
}
Then use AutoMapper to project (it will do any required includes so if you changed the VM later then it would handle that)
_db.Words.ProjectTo<WordVM>().ToList();
You don't need the NotMapped property AutoMapper would map the navigation property Author and the Author Property UserName to AuthorUserName

My workaround was to get all the related entities with .include(), then loop over them and omit the property values I did not want to return. It would require some maintenance in case your model changed, but surprisingly, it did not impact the response time dramatically.

Related

How to I return a List of DTOs from my API controller

I have a class :
public class Participant
{
[Key]
public int ParticipantId { get; set; }
[Column(TypeName ="nvarchar(50)")]
public string Email { get; set; }
[Column(TypeName = "nvarchar(50)")]
public string Name { get; set; }
public int Score { get; set; }
public int Timetaken { get; set; }
}
My endpoint:
public IActionResult GetAll()
{
var parts = _context.Participants.ToList();
if(!parts.Any())
return NotFound();
var participant = new ParticipantDto(parts);
return Ok(participant);
}
My Participant Dto:
public class ParticipantDto
{
private readonly Participant _participants;
public ParticipantDto(Participant participants)
{
_participants = participants;
}
}
I am trying the approach of passing the Participant object in the constructor and then assigning the Participant properties to DTO. I am aware how to do it for one Participant :
public string EmailAddress = _participants.Email;
etc
However, what If I want to return a List, how do I need to update my Dto to handle that?
For this statement,
var participant = new ParticipantDto(parts);
it is incorrect as you are passing the List<Participant> instance to the ParticipantDto constructor which the constructor is expected for the parameter value with the Participant type. You will get the compiler error for the unmatching type.
Hence, you need to iterate the list to transform each Participant element to the ParticipantDto type. You can use .Select() from System.Linq to do the iteration and transformation.
public IActionResult GetAll()
{
var parts = _context.Participants.ToList();
if(!parts.Any())
return NotFound();
List<ParticipantDto> participants = parts
.Select(x => new ParticipantDto(participant))
.Tolist();
return Ok(participants);
}
While in ParticipantDto, you need to perform the value mapping between the properties.
public class ParticipantDto
{
public ParticipantDto(Participant participant)
{
Email = participant.Email;
Name = participant.Name;
// Following assigning value from the properties of Participant to properties of ParticipantDto
}
public string Email { get; set; }
public string Name { get; set; }
// Following properties
}
Bonus:
You may look for AutoMapper which is a popular library for mapping between classes. (Wouldn't cover too much as the answer aims to focus and fix your current problem)

Map JSON column from MySql Database to C# Class from Web Api

I have a MySql database with columns Id int and Name:json
Places Table Sample
Id Name
1 {"en":"Sphinx","ar":"أبو الهول","fr":"Le sphinx"}
C# Place class
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
}
I'm connecting with EntityFramework 6 and connection success and retrieve data like this
{Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" }
What I want how to Map Name to new Object not JSON string
something like this
Place class
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
}
Localized class
public class Localized<T>
{
public T en { get; set; } // english localization
public T ar { get; set; } // arabic localization
public T fr { get; set; } // french localization
}
when I do this Name property come with NULL value
Code in Repository
using (var context = new PlacesEntityModel())
{
return context.Places.Take(5).ToList();
}
I don't want to use AutoMapper,
I want something in EntityFramework to select only one language in Database Level without fetching all other data and then map it
how to fix this?
You can try extension method to map from your entity type.
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
}
public class PlaceDTO
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
}
public class Localized<T>
{
public T en { get; set; } // english localization
public T ar { get; set; } // arabic localization
public T fr { get; set; } // french localization
}
Extenstion Method ToDto
public static class Extensions
{
public static PlaceDTO ToDto(this Place place)
{
if (place != null)
{
return new PlaceDTO
{
Id = place.Id,
Name = JsonConvert.DeserializeObject<Localized<string>>(place.Name)
};
}
return null;
}
}
Usage
var place = new Place() { Id = 1, Name = "{\"en\":\"Sphinx\", \"ar\":\"أبو الهول\", \"fr\":\"Le sphinx\"}" };
var placeDTO = place.ToDto();
Console.WriteLine($"{placeDTO.Id}-{placeDTO.Name.ar}-{placeDTO.Name.en}-{placeDTO.Name.fr}");
First of all, by using a class with a property per language, you restrict yourself. You'd always have to add new properties if you add new languages, which would of course be feasible, but unnecessary complicated. Furthermore you'd usually have the language as a string-ish object (or be able to convert), hence this would lead to code like this
Localized<string> name = ...;
switch(language)
{
case "en":
return name.en;
case "ar":
return name.ar;
case "fr":
return name.fr;
default:
throw new LocalizationException();
}
which is error-prone and overly complicated. For your problem, I think I'd opt to use some kind of dictionary
IDictionary<string, string> names = ...;
if(names.ContainsKey(language))
{
return names[language];
}
else
{
throw new LocalizationException();
}
which is easily extensible by just adding more translations to the dictionary.
To convert your JSON string to an IDcitionary<string, string>, you could use the following code
localizedNames = JObject.Parse(Name)
.Children()
.OfType<JProperty>()
.ToDictionary(property => property.Name,
property => property.Value.ToString());
From within your class this would effectively be
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
public Dictionary<string, string> LocalizedNames
{
get
{
return JObject.Parse(Name)
.Children()
.OfType<JProperty>()
.ToDictionary(property => property.Name,
property => property.Value.ToString());
}
}
}
The localized values can be accessed like
var localizedPlaceName = place.LocalizedNames[language];
Please note: Depending on your needs and use cases, you should consider the following issues:
Caching
In my snippet, the JSON string is parsed every time the localized names are accessed. Depending on how often you access it, this might be detrimental to performance, which could be mitigated by caching the result (don't forget to delete the cache when Name is set).
Separation of concerns
The class as is is supposed to be a pure model class. You might want to introduce domain classes that encapsulate the presented logic, rather than adding the logic to the model class. Having a factory that creates readily localized objects based on the localizable object and the language could be an option, too.
Error handling
In my code there is no error handling. Depending on the reliability of input you should consider additional error handling.
devart.com/dotconnect/mysql/docs/EF-JSON-Support.html
Like what #Nkosi said
In that case then, take a look at this article devart.com/dotconnect/mysql/docs/EF-JSON-Support.html
It probably can given that the library was able to build that feature in. You would need to figure out what they they did (reverse engineer)
I usually just use JSON.Net, I notice that another answer referenced JObject, but without going into whether your data-model is the right model, I generally find that you can do:
var MyObjectInstance = JObject.Parse(myJsonString).ToObject<MyObjectType>();
I notice that you have ComponentModel attributes on your class. I don't know off hand how many of these JSon.Net supports, and you'd have to research that. It definitely supports some attributes from XML serialization, and also has some of it's own.
Note that you can also convert a JSOn array into a list:
var MyObjectList = JArray.Parse(myJsonString).ToObject<IEnumerable<MyObjectType>();
I want something in EntityFramework to select only one language in
Database Level without fetching all other data and then map it
if you want it to be from database level, you can always create a view and then include this view in your project.
Example :
CREATE VIEW `PlacesLocalized` AS
SELECT
Id
, TRIM(REPLACE(name->'$.en', '"','')) AS en
, TRIM(REPLACE(name->'$.ar', '"','')) AS ar
, TRIM(REPLACE(name->'$.fr', '"','')) AS fr
FROM
places
This would create a model class Like :
public class PlacesLocalized
{
public int Id { get; set; }
public string en {get; set;}
public string ar {get; set;}
public string fr {get; set;}
}
Then, you can do :
var places = context.PlacesLocalized.Where(x=> x.en == "Sphinx");
But if you don't have enough permissions to do this in the database level, then you would need to specify the query in your EF. There is no easy way to change the execution logic of Entity Framework just for specific classes. That's why Entity Framework included SqlQuery method, which would give more flexibility to have custom queries when needed (like yours).
So, if you need to specify the localization from Entity Framework, then you would do a repository class to specify all custom queries you need including creating any DTO needed.
The basic way would be something like this :
public enum Localized
{
English,
Arabic,
French
}
public class PlaceRepo : IDisposable
{
private readonly PlacesEntityModel _context = new PlacesEntityModel();
public List<Place> GetPlacesLocalized(Localized localized = Localized.English)
{
string local = localized == Localized.Arabic ? "$.ar"
: localized == Localized.French ? "$.fr"
: "$.en";
return _context.Places.SqlQuery("SELECT Id, name-> #p0 as Name FROM places", new[] { local })
.Select(x=> new Place { Id = x.Id, Name = x.Name.Replace("\"", string.Empty).Trim() })
.ToList();
}
private bool _disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.Dispose();
}
_disposed = true;
}
}
~PlaceRepo()
{
Dispose(false);
}
}
now, you can do this :
using(var repo = new PlaceRepo())
{
var places = repo.GetPlacesLocalized(Localized.Arabic);
}
public class Place
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; }
public static explicit operator Place(PlaceDTO dto)
{
return new Place()
{
Id = dto.Id,
Name = dto.Name
};
}
}
public class PlaceDTO
{
[Key, Column("id")]
public int Id { get; set; }
[Column("name")]
public Localized<string> Name { get; set; }
public static explicit operator PlaceDTO(Place pls)
{
return new PlaceDTO()
{
Id = pls.Id,
Name = pls.Name
};
}
}
var placeDTO = (placeDto)place;
we can achieve this using explicit operator without using auto mapper

Model binding of nested properties in asp.net core 2.2

I'm trying to create a common complex object for my models (action parameters) and reuse it in many places.
Here is some sample code:
[HttpGet("/api/values")]
public ActionResult<string> Get([FromQuery] MyModel model) {
var sb = new StringBuilder();
sb.AppendLine(model.Id);
sb.AppendLine($"{model.Id}-{model.Generated?.DateStart}-{model.Generated?.DateEnd}");
sb.AppendLine($"{model.Id}-{model.Reference?.DateStart}-{model.Reference?.DateEnd}");
return sb.ToString();
}
public class MyModel {
public string Id { get; set; }
public DateInfo Generated { get; set; } = new DateInfo();
public DateInfo Reference { get; set; } = new DateInfo();
}
public class DateInfo {
public DateTime? DateStart { get; set; }
public DateTime? DateEnd { get; set; }
public RelativeTime? RelativeTime { get; set; }
}
Imagine the DateInfo class would have validation and common properties to be used in many models.
Adding [FromQuery(Name = "Something")] to the nested properties does the trick for swagger, but it makes it impossible to have two nested properties with the same type.
UDPATE:
I understand that adding the fully qualified property name (.../values?Id=1&Generated.DateInfo=2&Reference.DateInfo=3) would make it work, but that would be a really ugly way to call any API. Hyphens are the way, not dots.
I would like to map the binding in the same way as mapping a regular property.
How to achieve that?
I see two options.
Option 1: Just create a new, flattened class {Id, Foo, Bar} to use as the parameter of your action method. You can then map that to MyModel. That's the approach I would recommend as most maintainable.
Option 2: Custom model binding, as follows:
[ModelBinder(BinderType = typeof(MyModelBinder))]
public class MyModel
{
public string Id { get; set; }
[FromQuery]
public Info ComplexNestedProperty { get; set; }
}
public class AuthorEntityBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var model = new MyModel
{
Id = bindingContext.ValueProvider.GetValue("id"),
ComplexNestedProperty = new Info
{
Foo = bindingContext.ValueProvider.GetValue("foo"),
Bar = bindingContext.ValueProvider.GetValue("bar")
}
};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
As an expansion on Option 2 you could reasonably write some reflection that gets all the leaf property names of your nested model.

Using only a few properties from Model in my ViewModel in MVC

I am getting stuck using a ViewModel. Suppose I want to give a logged-in person an Edit Form with only a few of the fields from my Person domain model (maybe I want to custom annotate validations in the ViewModel).
I am getting stuck in two separate places in the controller (I have marked them with "<<< >>>").
When I pass the whole Person object as a property to my ViewModel, I know what to do. I can get my code to only update the name fields, but then I have lost my ability to validate the individual properties in my ViewModel. On the hand if I limit the properties in my ViewModel to only to a few properties, then my code in the GET section where I cann vm.Person doesn't work, since I am not passing the Person.
I scanned many examples on SO, but they were all using AutoMapper. Can I accomplish this without a mapper, and/or how do I write my own? And thanks in advance!
Model:
public class Person()
{
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string Email { get; set; }
}
ViewModel:
public class LoggedInPersonViewModel()
{
public int PersonId { get; set; }
[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
public string FirstName { get; set; }
}
Repository:
public Person GetLoggedInPerson()
{
var user = HttpContext.Current.User.Identity;
var userid = user.GetUserId();
return db.People.SingleOrDefault(i => i.UserId == userid);
}
Controller:
public class RegistrationController : Controller
{
//Get Logged in User, Edit Form
public ActionResult UpdateDetails()
{
LoggedInPersonViewModel vm = new LoggedInPersonViewModel();
<<<Do I also need a Person property in my ViewModel>>>
vm.Person = repository.GetLoggedInPerson();
return View(vm);
}
//POST
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UpdateDetails(LoggedInPersonViewModel loggedinpersonviewmodel)
{
if (ModelState.IsValid)
{
<<<what do i do here? is this correct? Again I cannot use Person if not in my VM.>>>
//Person person = db.People.Find(loggedinpersonviewmodel.PersonId);
//Person.FirstName = loggedinpersonviewmodel.FirstName;
//Person.LastName = loggedinpersonviewmodel.LastName;
//db.Entry(person).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index", "Person");
}
return View(loggedinpersonviewmodel);
}
}
}
Is there a way...or do I have to use AutoMapper for this?
I think you need to Map LoggedInPersonViewModel and Person. Example
public ActionResult UpdateDetails()
{
var person = repository.GetLoggedInPerson();
LoggedInPersonViewModel vm = new LoggedInPersonViewModel();
vm.PersonId = person.PersonId;
//Rest of properties
...
//return view model
return View(vm);
}
I would recommend AutoMapper this type of work. i.e. AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another.

Can't serialize an object

I defined a model like this
public class Planilla
{
[Key]
public int IDPlanilla { get; set; }
[Required(ErrorMessage = "*")]
[Display(Name = "Dirección de Negocio")]
public int IDDireccionDeNegocio { get; set; }
[Required (ErrorMessage = "*")]
public string Nombre { get; set; }
[Display(Name = "Descripción")]
public string Descripcion { get; set; }
public bool Activo { get; set; }
[ScriptIgnore]
public virtual DireccionDeNegocio DireccionDeNegocio { get; set; }
}
And I have a method in my controller that returns the first element of this model
[HttpPost]
public ActionResult GetElements(string IDCampana)
{
Planilla query = db.Planillas.First();
return Json(query);
}
My problem is when I invoke this method from client side throws an error that say's
circular reference is detected trying to serialize
System.Data.Entity.DynamicProxies.Planilla_7F7D4D6D9AD7AEDCC59865F32D5D02B4023989FC7178D7698895D2CA59F26FEE
Debugging my code I realized that the object returned by the execution
of the methodFirstit's a
{System.Data.Entity.DynamicProxies.Planilla_7F7D4D6D9AD7AEDCC59865F32D5D02B4023989FC7178D7698895D2CA59F26FEE}
instead a Model of my namespace like
Example.Models.DireccionDeNegocio`.
Why am I doing wrong?? Because I tried with other models and work's well
Use view models, that's the only advice I can give you. Never pass domain models to your views. It's as simple as that. And if you respect this simple rule and fundamental rule in ASP.NET MVC applications you will never have problems. So for example if you need only the id and the description in your view:
[HttpPost]
public ActionResult GetElements(string IDCampana)
{
Planilla query = db.Planillas.First();
return Json(new
{
Id = query.IDPlanilla,
Description = query.Description
});
}
Notice that in this case the anonymous object serves as view model. But if you really wanted to do things properly you would write your view model:
public class PlanillaViewModel
{
public int Id { get; set; }
public string Description { get; set; }
}
and then:
[HttpPost]
public ActionResult GetElements(string IDCampana)
{
Planilla query = db.Planillas.First();
return Json(new PlanillaViewModel
{
Id = query.IDPlanilla,
Description = query.Description
});
}
By the way Ayende wrote a nice series of blog posts about this.
System.Data.Entity.DynamicProxies.* is the Entity Framework proxy namespace. Your DbContext creates your entities as such to support lazy loading and change tracking. This isn't your problem. The problem likely lies in a circular association.

Categories

Resources