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.
Related
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;
I'm learning to use EFCore with DDD and Clean Architecture and while it is nice to that I don't have separate data models I am currently experiencing that properties added to the models result in automatically being added to the migrations.
I know I can ignore them by using attributes, but I would like to avoid having an entity framework library being referenced in the library with the models. So I would have to explicitly remove them in the DbContext or IEntityTypeConfiguration<TType>.
But I was wondering if it is possible to disable the default behavior completely and that I have to define everything in the DbContext or IEntityTypeConfiguration<TType>so I can safely create features so I won't pollute my database and my migrations with properties that should not be stored.
I would rather have that behavior turned off and be more explicit than having magically decided for me. (Been bitten with uncontrolled behavior like that before)
Example:
Lets say I have a class Sample which is set in the DbContext in a DbSet<Sample> Samples { get; set; }:
public class Sample
{
public Guid Id { get; set; } = new Guid();
public long Value { get; set; }
}
Now when I add the following two things they get automatically added in migrations
public class Sample
{
public Guid Id { get; set; } = new Guid();
public long Value { get; set; }
public string Remark { get; set; }
// Assume this is lazy computed for business logic in domain and application layer
public long LazyComputed { get { return Value * 100 ; } set { Value = Value * 100; } }
// Set of foos but should not be stored in data yet due to privacy for example
public IEnumerable<Foo> LogicFoos { get; set; } = new List<Foo>();
}
public class Foo
{
public Guid Id { get; set; } = new Guid();
}
Now when I add a migration it adds the Remark like I want to, but it sadly also adds the LazyComputed and a new relation with a new table Foos.
When it is a just one, or when I have a few, it is easy to manage fine. But when I have many and change several due a feature change there can be a lot of automatically adding/removing/changing, it seems easy to accidentally add fields to the migrations and repository when don't want.
Because of this I would like to be the default behavior to include to be reversed.
I am making an app using the ASP.Net Boilerplate framework and in my Domain layer I have a simple "Boss" entity. Creating and retrieving these entities from the database works fine but I can't get the "Update" to work. When map my "UpdateBossDto" to a Boss object and try to update it I get this error:
$exception {System.InvalidOperationException: The instance of entity
type 'Boss' cannot be tracked because another instance with the same
key value for {'Id'} is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached. Consider using
'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the
conflicting key values.
This error gets thrown in the BossManager class (I have removed the other methods for readability.
public class BossManager : DomainService, IBossManager
{
private readonly IRepository<Boss> _repositoryBoss;
public BossManager(IRepository<Boss> repositoryBoss)
{
_repositoryBoss = repositoryBoss;
}
public void Update(Boss entity)
{
_repositoryBoss.UpdateAsync(entity);
}
}
Here is my Update method in the BossAppService (i know getting the Id this way probably isn't great but right now I'm just desperate):
public void Update(UpdateBossDto updatedBoss)
{
var boss = new Boss();
updatedBoss.Id = _bossManager.GetBossIdByName(updatedBoss.Name);
boss = ObjectMapper.Map<Boss>(updatedBoss);
_bossManager.Update(boss);
}
And my UpdateDto class which holds the same attributes as the Boss class itself:
public class UpdateBossDto
{
public int Id { get; set; }
public string Name { get; set; }
public int Hp { get; set; }
public int CombatLvl { get; set; }
public int MaxHit { get; set; }
public string AttackStyle { get; set; }
public string Weakness { get; set; }
public string ImageUrl { get; set; }
}
How can I update the Boss object either with or without the Id? Any help would be greatly appreciated!
There's a number of issues here. First, the id should be coming from the request URL, since it uniquely identifies the resource that's being modified. This also saves you from having to do silly things like GetBossIdByName. Not only does that require an unnecessary query, but it's prone to error. The id is your key for a reason: it's unique. Names are not. You could have multiple bosses with the same name. Additionally, your name columns are likely not indexed, which means such a query is vastly more inefficient. Then, with your id, you should be querying the corresponding Boss out of your database, and mapping onto this instance, not creating a new instance. Finally, save that same instance back to the database. Then, you will have no issues.
When I insert my objects, they recognize they are one-to-many and the foreign key is correctly placed in the many side table.
When I retrieve my objects, they do not recognize the one-to-many on the one side table so I cannot access the ICollection of the many side objects. Specifically a Null Reference Exception is thrown when trying to access the collection/
In the explanation below, Incident is the one side and Disturbance is the many side. An Incident is associated with many Disturbances, but a Disturbance is a part of only one Incident.
Disclaimer: due to some project constraints and some modules being built on top of other modules we are using Entity Framework in our DAL and have models cross cutting Business/Data. This may factor into the issue. I'm aware this isn't ideal, but this is where we are at and I haven't seen anything that explicitly says you cannot use EF like this.
I have an Incident defined like this:
public class Incident
{
public Incident()
{
}
public Incident(List<Disturbance> sortedDisturbances)
{
StartTime = sortedDisturbances[0].StartTime;
Disturbances = new List<Disturbance>(sortedDisturbances);
}
[Key]
public int IncidentID { get; set; }
public virtual ICollection<Disturbance> Disturbances { get; set; }
[Column(TypeName="datetime2")]
public DateTime? StartTime { get; set; }
}
I had to add a parameterless constructor to deal with errors resulting from Entity Framework trying to use a parameterless constructor in certain areas.
I have a Disturbance defined like this :
public class Disturbance : IComparable<Disturbance>
{
[Key]
public int DisturbanceID { get; set; }
[Column(TypeName = "datetime2")]
public DateTime StartTime { get; set; }
[Column(TypeName = "datetime2")]
public DateTime EndTime { get; set; }
public int CompareTo(Disturbance other)
{
if (this.StartTime < other.StartTime)
return 1;
if (this.StartTime > other.StartTime)
return -1;
return 0;
}
}
I haven't read anything that said implementing an interface would break anything in Entity Framework so I did it.
This is how I add an Incident:
Business Layer:
private void MakeIncident(List<Disturbance> DisturbancesToAggregate)
{
Incident incidentToInsert = new Incident(DisturbancesToAggregate);
_iDAL.InsertIncident(incidentToInsert);
}
Data Layer:
public void InsertIncident(Incident incidentToInsert)
{
using (var context = new InternalContext())
{
context.Incident.Add(incidentToInsert);
context.SaveChanges();
}
}
The problem is that when I access my Incidents:
public IEnumerable<DomainModel.Disturbance> GetProcessedDisturbances()
{
List<DomainModel.Disturbance> processedDisturbances = new List<DomainModel.Disturbance>();
using(var context = new InternalContext())
{
foreach(var i in context.Incident)
{
foreach(var d in i.Disturbances)
{
processedDisturbances.Add(d);
}
}
}
return processedDisturbances;
}
The i.Disturbances Collection causes a Null Reference Exception. Is there something I need to call to force the context to get the Disturbances? Am I doing something blatantly wrong?
My ideas (I don't like any of them and don't want to do any of them):
1. Explicitly put the IncidentID on the Disturbance table (not even sure if this would work)
2. Force a lookup table by adding an ICollection of Incidents to Disturbances (its not a many-to-many relationship and I think this would prevent me from being able to clear all Disturbances from an Incident)
3. Explicitly define the relationship when the model is created. (I don't like the idea of having to do this, plus I think EF is half way there because it is inserting correctly.
Its happening because of lazy loading in EF. We need to Eagerly loading the data. To know more about them, please refer the link below.
https://msdn.microsoft.com/en-in/data/jj574232.aspx
I have a exceedingly simplistic data model (below). I am having trouble figuring out how I am to get my navigational properties to load from the database. I have no trouble getting them in, but the navigational property does not get set by EF it appears. I have seen several related questions, but they are slightly different or rather involved. I am looking for information on how navigational properties are treated by EF 4.2 (POCO). In the reading I've done, I got the impression that I would be able to access objects with foreign keys using navigational properties. Instead, my properties are coming back as either null or empty depending on if I instantiate my collection in the constructor.
public class AnimalDb : DbContext
{
public static AnimalDb Create(string fileName)
{
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
return new AnimalDb(fileName);
}
private AnimalDb(string fileName) : base(fileName) { }
public DbSet<Animal> Animals { get; set; }
}
public class Animal
{
public Animal()
{
Id = Guid.NewGuid();
Traits = new ObservableCollection<Trait>();
}
public Guid Id { get; set; }
public string Species { get; set; }
public string Name { get; set; }
public ObservableCollection<Trait> Traits { get; set; }
}
public class Trait
{
public Trait()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public string Name { get; set; }
}
And here is some (simple) code that uses it:
foreach (var animal in db.Animals)
{
foreach (var trait in animal.Traits)
{
//animal.Traits count is 0, so this does not run.
//However there are traits in the database, as my populate
//function is working fine.
Console.WriteLine("{0} is {1}", animal.Name, trait.Name);
}
}
----Edit Answer Summary----
Using the article and information provided in the answers below, I was able to discover I could either eagerly load using db.Animals.Include() or enable lazy loading. There is a trick to enabling lazy loading and being able to use it though. First to enable lazy loading I added:
db.Configuration.LazyLoadingEnabled = true;
Next I changed my Traits collection in the following manner:
public virtual ObservableCollection<Trait> Traits { get; set; }
Making it virtual allows the automatically generated proxy to lazily load Traits. That's it! IMHO I think the MSDN docs should shout this load and clear in the POCO EF 4.2 coding conventions. Again thanks for the help.
There are a few reasons that your wire-up methods may appear to have no data. To load related data you need to :
explicity load the data
meet the lazy loading requirements, or
use eager loading using Include()
My guess is that you turned off the virtual proxies. There is more on the requirements here:
http://msdn.microsoft.com/en-us/library/dd456855.aspx
If you don't use lazy loading you have to explicitly tell EF to load the relation with the Include method:
foreach (var animal in db.Animals.Include(a => a.Traits))
{
foreach (var trait in animal.Traits)
{
//...
}
}
You can read more about eager loading in this article.