I'm getting started with ASP.NET Core and EF Core 6. I'm trying to build a simple web api whith two models and an 1-n relationship between the models.
Model A:
public class ModelA
{
public Guid Id { get; set; }
public string StringProperty { get; set; }
public ICollection<ModelB> ModelBs { get; set; }
}
Model B:
public class ModelB
{
public Guid Id { get; set; }
public string StringProperty { get; set; }
public Guid ModelAId { get; set; }
public ModelA ModelA { get; set; }
}
When I try to create a ModelB by using the POST-endpoint of the ModelB-Controller, it expects me to pass the ModelA as well. If I do provide it, I get a duplicate key error because EF tries to create a new ModelA in the database, which causes a duplicate key error.
I must only use the ModelB-model as parameter for the post method and explicitly must not use any kind of intermediate model.
I would like to only use the ModelA id, not the entire ModelA object:
//Desired post-request
{
"stringProperty": "value",
"modelAId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
Making the ModelA reference nullable allows for post-requests as described above:
public class ModelB
{
public Guid Id { get; set; }
public string StringProperty { get; set; }
public Guid ModelAId { get; set; }
public ModelA? ModelA { get; set; }
}
But that feels wrong since an instance of ModelB must not exist without a reference to a ModelA instance.
Is there any way to achieve this without using DTOs or making the reference nullable?
I'm probably missing something trivial here.
I think you should star to use DTOs on your project! For example, model B would have only the data necessary to create the item, i.e StringProperty and ModelAId, and inside of your controller, you would associate with the existing ModelA.
You can have a look on the entity framework on the link below.
https://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx
Even if you create a DTO(highly recommended, passing a whole another object just to show relationship is a waste of bandwidth and bad practice ) or not you will accept ModelAId from the user, not the whole object anyway so.
Edit:
if you want to trick the product owner. Just create a base class without ModelA prop and all the rest props in ModelB now make ModelB inherit this and add ModelA explicitly. Now create ModelBPost also inheriting from this and use this as a parameter for POST data this way the product owner knows the fields are exactly the same and you pass the verification error.
Old Answer
How about you get the model A fresh from DB and assign that to ModelB
like
public IAction PostModelB(ModelB modelB)
{
modelB.ModelA = context.ModelAs.First(x => x.Id == modelB.ModelAId);
//now since efcore is tracking this it know this object already exists
context.ModelBs.Add(modelB);
}
However, sometimes EFCore screws up and you get an already tracked error(which is quite unfortunate after so much time it still can't do it properly). If this happens(which it might since you are persistent on not using a DTO and accept a whole object just for relationship) you will have to set the navigation property null and only use the ModelAId property to insert the new record or you can get the instance which efcore holds:
var modelA = context.ModelAs.First(x => x.Id == modelB.ModelAId);
var trackedinstance = context.ChangeTracker.Entry(modelA)?.Entity ?? modelA;
modelB.ModelA = trackedinstance;
modelB.ModelAId = modelA.Id;
Related
guys.
I have a question about EF Core DbCommandInterceptor.
Let's have a class with 2 fields like this
public class User
{
public Guid Id { get; set; }
public string SameData { get; set; }
}
public class TestClass
{
public Guid Id { get; set; }
public Guid TestClassId { get; set; }
[NotMapperd, MyAttr]
public TestClass TestClass { get; set; }
}
where User and TestClass are both located in the different contexts (for example, UserDbContext, TestDbContext). MyAttr is the marker attribute, nothing more.
So, I want to write an interceptor that raises up each time we try to get info about TestClasses, but after data got it should get an additional data about User with cross-request to UserDbContext (It possible, because I have User Id after the command execution and can use this Id in the request)
I know, that it should be DbCommandInterceptor.ReaderExecuted or DbCommandInterceptor.ReaderExecutedAsync in this case, but I cannot understand how to get information about objects in the result (I can get rows but I cannot understand what should I do, how should I map it). I can use additional libraries in the project if needed (like Dapper and others).
Could anyone helps me to get
Result Type - concrete entity type or entity collection type?
Result as a C# object (POCO or POCO collection)?
Thank you.
I know, that it should be DbCommandInterceptor.ReaderExecuted or DbCommandInterceptor.ReaderExecutedAsync in this case, but I cannot understand how to get information about objects in the result
The EF Command interceptors don't support that. You can replace the DataReader with a different one. But the query was generated to fill a particular object graph, which is not exposed by the interceptor API.
I am working on .net core Web API project. I have two classes:
public class CategoryMasterDto : CommonInfoDto
{
public int CategoryId { get; set; }
[StringLength(50)]
public string CategoryName { get; set; }
public string CategoryImage { get; set; }
}
public class CommonInfoDto
{
public Guid CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public Guid ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
public short Status { get; set; }
}
Now, I want to restrict users to specify values for certain properties from Web API like CategoryId and all properties of CommonInfoDto as I will specify all these properties from my side before inserting data in the database.
I tried using JsonIgnore using which properties will not be visible in the tools like swagger. But again if the user manually specified those properties, the values will be received on the server-side.
For e.g. if I apply JsonIgnore to CategoryId, it will no be visible in swagger but if the user adds the property CategoryId and pass some value, it will be received on the server-side.
I want to achieve two things:
Restrict the users to pass values for certain properties or even if it is passed, those should not be bind to the properties on the server-side during the POST and PUT request.
I want to pass all properties when user requests for GET request.
I already have one solution i.e. to create one DTO for GET and another for POST/PUT. Is there any better solution through which I can use the same DTO for both and achieve what I want.?
You need just one DTO class for GET, POST/PUT. And, you could use AutoMapper tool to configure mapping from model domain class to DTO class, and back. And then, while configuring mapping from DTO to domain model class, you could ignore certain parameters.
CreateMap<CommonInfoDto, CommonInfo>().ForMember(x => x.Guid, opt => opt.Ignore());
It means that you would read data from database (your domain model class), pass it to DTO class with all the parameters; but when you would recieve data from from user (DTO class), while saving you will not use its Guid (because it will be ignored during mapping).
As what everybody is suggesting. This can only be Achieve using 2 classes. Input.Model and Domain.Model, then use Automapper to automatically map properties to your actual Domain.Model during input process (POST/PUT) while you return you Domain.Model in your GET endpoints.
Can anyone provide an easier more automatic way of doing this?
I have the following save method for a FilterComboTemplate model. The data has been converted from json to a c# model entity by the webapi.
So I don't create duplicate entries in the DeviceProperty table I have to go through each filter in turn and retrieve the assigned DeviceFilterProperty from the context and override the object in the filter. See the code below.
I have all the object Id's if they already exist so it seems like this should be handled automatically but perhaps that's just wishful thinking.
public void Save(FilterComboTemplate comboTemplate)
{
// Set the Device Properties so we don't create dupes
foreach (var filter in comboTemplate.Filters)
{
filter.DeviceProperty = context.DeviceFilterProperties.Find(filter.DeviceFilterProperty.DeviceFilterPropertyId);
}
context.FilterComboTemplates.Add(comboTemplate);
context.SaveChanges();
}
From here I'm going to have to check whether any of the filters exist too and then manually update them if they are different to what's in the database so as not to keep creating a whole new set after an edit of a FilterComboTemplate.
I'm finding myself writing a lot of this type of code. I've included the other model classes below for a bit of context.
public class FilterComboTemplate
{
public FilterComboTemplate()
{
Filters = new Collection<Filter>();
}
[Key]
public int FilterComboTemplateId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public ICollection<Filter> Filters { get; set; }
}
public class Filter
{
[Key]
public int FilterId { get; set; }
[Required]
public DeviceFilterProperty DeviceFilterProperty { get; set; }
[Required]
public bool Exclude { get; set; }
[Required]
public string Data1 { get; set; }
}
public class DeviceFilterProperty
{
[Key]
public int DeviceFilterPropertyId { get; set; }
[Required]
public string Name { get; set; }
}
Judging from some similar questions on SO, it does not seem something EF does automatically...
It's probably not a massive cut on code but you could do something like this, an extension method on DbContext (or on your particular dataContext):
public static bool Exists<TEntity>(this MyDataContext context, int id)
{
// your code here, something similar to
return context.Set<TEntity>().Any(x => x.Id == id);
// or with reflection:
return context.Set<TEntity>().Any(x => {
var props = typeof(TEntity).GetProperties();
var myProp = props.First(y => y.GetCustomAttributes(typeof(Key), true).length > 0)
var objectId = myProp.GetValue(x)
return objectId == id;
});
}
This will check if an object with that key exists in the DbContext. Naturally a similar method can be created to actually return that entity as well.
There are two "returns" in the code, just use the one you prefer. The former will force you to have all entities inherit from an "Entity" object with an Id Property (which is not necessarily a bad thing, but I can see the pain in this... you will also need to force the TEntity param: where TEntity : Entity or similar).
Take the "reflection" solution with a pinch of salt, first of all the performance may be a problem, second of all I don't have VS running up now, so I don't even know if it compiles ok, let alone work!
Let me know if that works :)
It seems that you have some common operations for parameters after it's bound from request.
You may consider to write custom parameter bindings to reuse the code. HongMei's blog is a good start point: http://blogs.msdn.com/b/hongmeig1/archive/2012/09/28/how-to-customize-parameter-binding.aspx
You may use the code in Scenario 2 to get the formatter binding to deserialize the model from body and perform the operations your want after that.
See the final step in the blog to specify the parameter type you want customize.
I have a strongly typed MVC3 Razor view in which I want to display one actionlink or another, depending on whether or not a child object exists in the model. I am unable to tell how to test for the object's existence, something I thought would be pretty straightforward. I'm using Entity Framework 4.1 to generate the underlying relationships, database, and entities.
My POCO classes (much abbreviated):
public class Pet
{
public int PetID { get; set; }
public virtual InsurancePolicy InsurancePolicy { get; set; }
}
public class InsurancePolicy
{
[ForeignKey(Pet)]
public int InsurancePolicyID { get; set; }
public virtual Pet Pet { get; set; }
}
In the view, I thought I wanted to evaluate something like:
#foreach(var item in Model)
{
#if(string.IsNullOrEmpty(item.InsurancePolicy.InsuranceID.ToString()))
{
#Html.ActionLink("action link to create new InsurancePolicy")
}
else
{
#Html.ActionLink("action link to edit existing InsurancePolicy")
}
}
Of course, if the Pet object has no associated InsurancePolicy yet, the conditional fails: Object reference not set to an instance of an object. I've been unable to find the equivalent of IsObject or any way to evaluate the nonexistence of the object without raising this error.
Can anyone point me to a way to make this work?
You're looking for
if (item.InsurancePolicy == null)
I've got this question that's been bugging me and my vast and unfathomable intellect just can't grasp it. The case: I want to make multiple one-to-many relationships to the same entity using the fluent nhibernate automapper and export schema.
I have:
Base Class:
public abstract class Post<T> : Entity, IVotable, IHierarchy<T>
{
public virtual string Name
{
get; set;
}
public virtual string BodyText
{
get; set;
}
public virtual Member Author
{
get; set;
}
}
and Inheriting Class:
[Serializable]
public class WallPost : Post<WallPost>
{
public virtual Member Receiver
{
get; set;
}
}
The 'Member' properties of WallPost is a foreign key relationship to this class:
public class Member : Entity
{
public Member()
{
WallPosts = new IList<WallPost>();
}
public virtual IList<WallPost> WallPosts
{
get; set;
}
}
I hope you're with me until now. When I run the exportschema of nhibernate I expect to get a table wallpost with 'author_id' and 'receiver_id' BUT I get author_id, receiver_id,member_id. Why did the Nhibernate framework add member_id, if it's for the collection of posts (IList) then how do you specify that the foregin key relationship it should use to populate is receiver, i.e. member.WallPosts will return all the wallposts of the receiver.
I hope i made sense, if you need anything else to answer the question let me know and I'll try to provide.
Thanks in advance
P.s. If I change the property name from 'Receiver' to 'Member' i.e. public virtual Member Member, only two associations are made instead of 3, author_id and member_id.
E
THE SOLUTION TO THIS QUESTION FOR ANYONE ELSE WONDERING IS TO ADD AN OVERRIDE:
mapping.HasMany(x => x.WallPosts).KeyColumn("Receiver_id");
Most likely (I don't use auto mapping, I perfer to write my own classmaps) it's because the auto mapping assuming that your Member's "WallPosts" is controled by the wall posts. So it creates a member_id for that relationship.
Edit: Try adding an override in your fluent config, after AutoMap add:
.Override<Member>(map =>
{
map.HasMany(x => x.WallPosts, "receiver_id")
.Inverse;
});