I'm creating an application using ASP.NET Core and Entity Framework. I have two models:
class Book
{
public int Id { get; set; }
public string Name { get; set; }
public List<Author> Authors { get; set; }
}
class Author
{
public int Id { get; set; }
public string Name { get; set; }
public Book Book { get; set; }
}
I want that when creating an author, his name cannot be created the same as the book. I want to add validation to Author.Name (Something like this: Author.Name != Book.Name.
Also, I want this rule to enter into the db too. So I think it possible to make in DbContext (OnModelCreating).
How can I do it? Thanks a lot!
Your Author class can implement the IValidatableObject interface.
This way, you will have a Validate method like this:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Author.Name == Book.Name)
{
yield return new ValidationResult(
$"Author name and Book name can't have the same value.",
new[] { nameof(Name) });
}
}
Then, you can manage the ModelState validation in your controller action as something like:
if (!ModelState.IsValid)
{
// Do something when validation is not ok
}
To prevent this to be commited on database side, it would be better to ensure the data consitency on database side by implementing a check constraint directly in SQL on your Author table.
Related
I have created a class and I'm putting a list of same type as a property of that class.
Is it good or bad practice?
I am putting the same type of list because of I want to manage everything by only one object.
I don't want to create a single object and a list of object of the same type.
Any help is highly appreciated!
class AssetSection
{
public string Code { get; set; }
public string Description { get; set; }
public string SITEID { get; set; }
public string PlantID { get; set; }
public string User { get; set; }
public string UpDateTime { get; set; }
public List<AssetSection> AssetSections { get; set; }
public AssetSection(string des, string code)
{
Description = des;
Code = code;
}
}
That's ok. If you can imagine, you can design and use it.
Let's talk about entity framework. We create 2 entities like this:
public class User : IdentityUser
{
[Key]
public string Id { get; set; }
public UserProfile Profile { get; set; }
}
public class UserProfile
{
[Key]
public string UserId { get; set; }
public User User { get; set; }
}
Now, when we try to get current user:
User user = await _userManager.GetUserAsync(User);
user becomes an instance of User class now. This instance has a property name Profile, and this property has another property name User which has a type User.
It's called mapping. So, to answer your question: You can use it. But I'm not saying it's good or not based on the way to design the model.
As a general observation, such a structure is known as a rose tree, or just a tree. It enables you to write code like this:
var t = new AssetSection("foo", "bar")
{
AssetSections = new List<AssetSection>
{
new AssetSection("baz", "qux")
{
new AssetSection("corge", "garply"),
new AssetSection("fred", "plugh")
{
AssetSections = new List<AssetSection>
{
new AssetSection("xyzzy", "thud")
}
}
},
new AssetSection("quux", "quuz")
{
new AssetSection("grault", "waldo")
}
}
};
If what you want to model is a tree-like structure like that, then it's fine. On the other hand, if such a hierarchy is not what you're trying to model, then it's likely to be confusing.
By the way, the code as proposed violates the .NET framework design guidelines:
DO NOT provide settable collection properties.
DO NOT use ArrayList or List<T> in public APIs
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
I need to check privileges to specific field in specific object in database.
Let's make and example. I have Model called Employee
public class Employee {
[Key]
public int EmployeeID { get; set; }
public string JobTitle { get; set; }
public string Description { get; set; }
public int Salary { get; set; } // <---- Restricted
public int BossID { get; set; }
}
And I have a few cases:
I need to restrict access to specific field Salary because I don't want anyone to see each other salary. But HR can see anyone Salary and edit it. If I'm this employee I can see my own Salary, but cannot edit it.
Everyone can see each other job titles, but only HR can edit it. And also boss of that employee, can edit, by employee himself cannot.
Use case:
I'm manager with RoleID 4. I want to see Salary of my Employee named John Smith with EmployeeID 5. I can do that.
I'm manager with RoleID 4. I want to see Salary of 'Employeenamed Mark Twain withEmployeeID` 8. Mark is not but my directly subordinate. He is from different branch. I cannot do that.
I'm employee with EmployeeID 5 and I want to see my Salary. That's allowed.
I'm employee with EmployeeID 5 and I want to edit my own Salary. It's forbidden. I get HTTP Error 401.
I'm from HR. I can see and edit Salary of all Employees in company.
I though of something like this:
public class Access {
[Required]
public int RoleID { get; set; }
[Required]
public string TableName { get; set; }
[Required]
public string ColumnName { get; set; }
[Required]
public int RowID { get; set; }
}
And then check (by Authorize attribute) if specific role (boss, HR or something) has access to specific field (for example Salary) for specific data (for example Employee with id 22). That's a lot of "specific"by the way.
How should I do it? Is my idea 'OK'?
In case when logic is less complicated or more generic, it's possible to set custom output formatter to prevent some fields to be written into the respose.
The approach has next problems:
Shouldn't handle complicated logic. As it causes business logic spread to the multiple places
Replaces default serialization. So if there are specific serialization settings are set in Startup, then it should be transfered
Let's see an example.
There could be a custom attrbute like
public class AuthorizePropertyAttribute : Attribute
{
public AuthorizePropertyAttribute(string role) => Role = role;
public string Role { get; set; }
}
Then output formatter could be like:
public class AuthFormatter : TextOutputFormatter
{
public AuthFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
SupportedEncodings.Add(Encoding.UTF8);
}
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new AuthorizedPropertyContractResolver(context.HttpContext.User)
};
await context.HttpContext.Response.WriteAsync(
JsonConvert.SerializeObject(context.Object, settings));
}
}
That would require
public class AuthorizedPropertyContractResolver : DefaultContractResolver
{
public AuthorizedPropertyContractResolver(ClaimsPrincipal user)
{
User = user;
}
public ClaimsPrincipal User { get; }
protected override JsonProperty CreateProperty(MemberInfo member,
MemberSerialization memberSerialization)
{
var result = base.CreateProperty(member, memberSerialization);
result.ShouldSerialize = e =>
{
var role = member.GetCustomAttribute<AuthorizePropertyAttribute>()?.Role;
return string.IsNullOrWhiteSpace(role) ? true : User.IsInRole(role);
};
return result;
}
}
Registration:
services.AddMvc(options =>
{
options.OutputFormatters.Insert(0, new AuthFormatter());
});
In that case Response for simple user will lack of the Salary field {"Id":1,"Name":"John"} at the same time manager will see the full response
{"Id":1,"Name":"John","Salary":100000}, ofcourse the property "Salary" should have attribute set
[AuthorizeProperty("Boss")]
public double Salary { get; set; }
You should implement 2 different methods. One is for the HR when requesting the data, and the other is for the simple user. Then you never should return the whole object (json), instead, create some DTOs (Data Transfer Object) that holds the desired data. So lets make an example:
public class DTOGetEmployeeByEmployee {
public int EmployeeID { get; set; }
public string JobTitle { get; set; }
public string Description { get; set; }
public int BossID { get; set; }
}
public class DTOGetEmployeeByHR {
public int EmployeeID { get; set; }
public string JobTitle { get; set; }
public string Description { get; set; }
public int Salary { get; set; }
public int BossID { get; set; }
}
Once a user requests that employee, get it from the Database, and then convert it into the desired DTO. Best way I saw so far is using AutoMapper to do so:
Mapper.Map<DTOxxxx>(yourObject);
You can also use the [Authorize] Attribute to check if the User is HR or an Employee. I did this multiple times combined with JWT-Token.
public class EmployeeController
{
[Authorize("HR")]
[HttpGet, Route("GetForHR")]
public IActionResult Get(int employeeID)
{
// Note: this is just a sample out of my head, so there will be adjustments needed in order to run that
// Check if the HR is allowed to access the Employees data
// Get the Employee by its ID
var emp = ...;
// Convert it to the DTO
var dto = Mapper.Map<DTOGetEmployee>(emp);
// return the dto
return Ok(dto);
}
}
I bet there are plenty of better solutions out there, but for me, this is super simple, wasy to reimplement in other applications and there is no palpable performance loss
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.
I'm trying to add a new item to an existent list in my MVC application, my problem that's I don't know the correct approach to do it.
I've done this so far:
My Customer code:
Controller
public class CustomerController : Controller
{
public CustomerBusiness customerBusiness { get; set; }
public CustomerController()
{
customerBusiness = new CustomerBusiness();
}
//Some code that makes CRUD and more these methods
[HttpGet]
public ActionResult ViewAllJobOfferts(int id)
{
var cust = customerBusiness.GetById(id);
return View(cust.JobOfferts);
}
public ActionResult CreateJobOffert(int id)
{
var cust = customerBusiness.GetById(id);
return View(cust);
}
/* [HttpPost]
public ActionResult CreateJobOffert(JobOffertModel jobOffert)
{
return View();
}*/
}
I have a relationship 1 to n between my entities Customer and JobOffert, and the method ViewAllJobOfferts works fine, but I got stuck when I try to add a new JobOffert.
I have a couple of questions, here we go:
I must to create a controller special to JobOfferts, or control
inside the CustomerController?
When I try to create the view that will submit the form to new
JobOffert I didn't know how to link the customer to this new
JobOffert, f I try create a page using customer model, I do not have the JobOffert attributes and if I create using the JobOffert model, I dont know how make the link between these two objects. how must I do this?
PS.: Here the code of both models:
JobOffert Model
Customer Model
I must to create a controller special to JobOfferts, or control inside
the CustomerController?
Not necessarily, controller like other class should follow SRP (Single Responsibility Principle). In this case as long as CustomerController facilitating information related to Customer, it's completely fine.
When I try to create the view that will submit the form to new JobOffert I didn't know how to link the customer to this new JobOffert, f I try create a page using customer model, I do not have the JobOffert attributes and if I create using the JobOffert model, I dont know how make the link between these two objects. how must I do this?
The link between the customer and JobOffer is as you defined One-to-Many and entities contains references to each other. E.g. You can find all JobOffer for a customer whose Id is 1024 by querying JobOffer table where customerID = 1024. Likewise each JobOffer is traceable by Customer reference in entity class.
Now about creating a new JobOffer for a customer this is how you can go about:
public class CustomerController : Controller
{
public CustomerBusiness customerBusiness { get; set; }
public CustomerController()
{
customerBusiness = new CustomerBusiness();
}
//Some code that makes CRUD and more these methods
[HttpGet]
public ActionResult ViewAllJobOffersForCustomer(int customerId)
{
ICollection<JobOfferModel> model = customerBusiness.GetAllJobOffersByCustomerId(customerId);
return View(model);
}
[HttpGet]
public ActionResult CreateJobOffer()
{
// Blank model object to accept values from user,
// you may like to create a view model based on UI needs.
JobOfferModel jobOfferModel = new JobOfferModel();
return View(jobOfferModel);
}
[HttpPost]
public ActionResult CreateJobOffer(JobOfferModel jobOffer)
{
// You get a filled object here that contains customer id and job offer details
customerBusiness.CreateJobOffer(jobOffer);
return RedirectToAction("ViewAllJobOffersForCustomer", new { customerId = jobOffer.CustomerId });
}
Sample business service class:
public class CustomerBusiness
{
public ICollection<JobOfferModel> GetAllJobOffersByCustomerId(int customerId)
{
// TODO: Fetch job offer details from persistent store
// E.g.
// dataContext.JobOffers.Where(x => x.CustomerId == customerId).ToList();
throw new NotImplementedException();
}
public void CreateJobOffer(JobOfferModel jobOffer)
{
// TODO: Add job offer details in persistent store
// E.g.
// dataContext.JobOffers.Add(jobOffer);
}
}
Modified entity classes:
public class JobOfferModel
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
[Required]
[DefaultValue(false)]
public bool Acepted { get; set; }
[Required]
[DefaultValue(true)]
public bool Active { get; set; }
[Required]
[Column(TypeName = "DateTime2")]
public DateTime JobDate { get; set; }
[ForeignKey("Id")]
public int CustomerId { get; set; }
public virtual CustomerModel Customer { get; set; }
}
public class CustomerModel
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "DateTime2")]
public DateTime BirthDate { get; set; }
public int PhoneNumber { get; set; }
public ICollection<JobOfferModel> JobOffert { get; set; }
}
So basically you will have a method in CustomerController that returns empty ViewModel or Model object. On view you will make customerId hidden. So that when form is posted it is mapped to correct customer along with JobOffer details. Once you have model object in HttpPost method you just need to insert an entry in JobOffer table (any persistent store) with customerId associated with it.
There are other nitty gritty but above typical approach will give you a good start I hope. Cheers