I have the following two models that I'm trying to setup, but cannot figure out how to do it using data annotations or fluent API. Can anyone advise either the proper annotations or Fluent API code?
public class Vehicle
{
public int ID { get; set; }
public int JobID { get; set; }
public int StatusID { get; set; }
public string Name { get; set; }
public virtual Job Job { get; set }
public virtual Status Status { get; set; }
}
public class Job
{
public int ID { get; set; }
public int VehicleID { get; set; }
public int StatusID { get; set; }
public string Description { get; set; }
public virtual Vehicle Vehicle{ get; set; }
public virtual Status Status { get; set; }
}
The problem I'm having is the reference to the Job model from Vehicle, and from Vehicle to Job. In the database, the Jobs tables holds all jobs pending or completed. There may or may not be a vehicle associated with it (when a job is in progress or complete, a vehicle will be associated with it). For the vehicles table, the JobID represents the current job assigned to the vehicle (if it's assigned a job) and will constantly change through the day, but should have no effect on the Jobs table.
The following snippet outlines a fluent code-first example of the relationship you describe:
public class Vehicle
{
public int ID { get; set; }
public string Name { get; set; }
public virtual Job Job { get; set; }
}
public class Job
{
public int ID { get; set; }
public string Description { get; set; }
}
public class AppDataContext : DbContext
{
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Job> Jobs { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Job>().HasKey(x => x.ID);
mb.Entity<Vehicle>().HasKey(x => x.ID);
mb.Entity<Vehicle>().HasOptional(x => x.Job).WithOptionalDependent();
// ... other config, constraints, etc
}
}
And to get Jobs associated with vehicles:
using (var context = new AppDataContext())
{
var query = context.Vehicles.Where(x => x.Job != null).Select(x => x.Job);
// ...
}
IMHO, the explicit foreign key properties should be kept out of the model unless strictly needed. This tends to make life easier and keeps the code clean.
Hope this is along the lines of what you're looking for...
P.S. - At the risk of 'just posting links' - If you already have a database (as implied in your question), it might be worth considering a Database First approach: http://msdn.microsoft.com/en-gb/data/jj206878.aspx ..or Code First with an existing database: http://msdn.microsoft.com/en-us/data/jj200620
Related
I'm having a problem with many-to-many relationships in a pizzeria system I'm developing.
I have an entity called "Payament" that has a list of Pizzas and a list of Drinks.
ex:
public class Payament
{
public string? PayamentId { get; set; }
public virtual List<Pizza> Pizzas { get; set; }
public virtual List<Drink> Drinks { get; set; }
public string? CPFId { get; set; }
[JsonIgnore]
public virtual Client? Client { get; set; }
public double? TotalPay { get; set; }
public DateTime DateTransaction { get; set; }
public virtual StatusOrder StatusOrder { get; set; }
public Payament()
{
Pizzas = new List<Pizza>();
Drinks = new List<Drink>();
DateTransaction = DateTime.Now;
StatusOrder = StatusOrder.CARRINHO;
}
}
Also, I have two entities Pizza and Drink.
ex:
public class Pizza : IItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public double Value { get; set; }
[JsonIgnore]
public virtual List<Payament> Payament { get; set; }
public Pizza()
{
Payament = new List<Payament>();
}
}
public class Drink : IItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public double Value { get; set; }
[JsonIgnore]
public virtual List<Payament> Payament { get; set; }
public Drink()
{
Payament = new List<Payament>();
}
}
These classes are configured as many-to-many in OnModelCreating()...
Drink:
builder.HasMany(h => h.Payament)
.WithMany(d => d.Drinks);
Pizza:
builder.HasMany(h => h.Payament)
.WithMany(p => p.Pizzas);
Payament:
builder.HasMany(p => p.Pizzas)
.WithMany(p => p.Payament);
builder.HasMany(d => d.Drinks)
.WithMany(d => d.Payament);
Migrations were generated, all right. But when I go to send a payment, I get this error:
MySqlException: Cannot add or update a child row: a foreign key constraint fails (pizzaroller.drinkpayament, CONSTRAINT FK_DrinkPayament_Payament_PayamentId FOREIGN KEY (PayamentId) REFERENCES payament (PayamentId) ON DELETE CASCADE)
What I believe it could be:
If I'm not mistaken, a drinkpayament table and a pizzapayament table are created where the primary keys between the relations are joined.
And in this case, "Payament" has a primary key of type string, while items "Pizza" and "Drink" have a primary key of type int.
The conflict is likely to be found in this divergence. I could be wrong, of course.
So I would like to know how I can solve this problem. And also, if possible, tips on how I can work with existing items for sale and relate them to a payment.
Project is on github if you want to take a look:
https://github.com/newhobbye/pizza-roller-api
This project is for the exercise of knowledge and some patterns that I will implement to practice. I accept any kind of criticism! A big hug and many thanks!
I managed to solve.
I was resending the client entity in update. And I hadn't turned off the AsNoTracking() option;
In addition to this problem, I was also resending the items instead of associating the existing ones by id.
Thank you all for your help!
When I try to save a Consumption object by calling context.Consumption.Add(myConsumption) and then context.SaveChanges(), I get a System.InvalidCastException. I have no clue why EF wants to cast the List<Payment> to Payment, since the Payments property of Consumption is of type List<Payment>.
public class Consumption {
public int Id { get; set; }
...
public virtual List<Payment> Payments { get; set; }
}
public class Payment {
...
[ForeignKey("Id")]
public Consumption Consumption { get; set; }
}
I think you have things a little out of wack
Assuming you have the following design
public class Consumption
{
public int Id { get; set; }
...
public virtual List<Payment> Payments { get; set; }
}
public class Payment {
public int Id { get; set; }
public Consumption Consumption { get; set; }
public int ConsumptionId { get; set; }
}
EF is smart enough to figure out the relationship and database scaffolding based on naming convention
However if you need to use Annotations, then you need to make a decision of how you are telling EF about your relationship
public class Payment {
public int Id { get; set; }
public Consumption Consumption { get; set; }
[ForeignKey("Consumption")] // point to the FK to the navigation property
public int ConsumptionId { get; set; }
}
Or
public class Payment {
public int Id { get; set; }
[ForeignKey("ConsumptionId")] // point to the navigation property to the FK
public Consumption Consumption { get; set; }
public int ConsumptionId { get; set; }
}
Or
public class Consumption
{
public int Id { get; set; }
...
[ForeignKey("ConsumptionId")] // Point to the dependent FK
public virtual List<Payment> Payments { get; set; }
}
My suspicion is that in your database (and this should be easy to check) you just have a singular foreign key in your Consumption table for payment
You seem to be pointing to the Primary key of your Payments table. However musings aside, it surprises me that EF even let you do this migration (if i'm reading this correctly)
I am trying to map a many to one relationship. I feel I'm close but not quite there.
I have this sensor table:
SensorId
FK_LocationId
Name
etc...
Which holds many data records in the Data Table.
DataId
FK_SensorId
Time
Value
And I am trying to create a Model for this.
public class DataSensor
{
public int Id { get; set; }
public int DataNodeId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool active;
public bool alarm;
}
public class GatheredData
{
public int Id { get; set; }
public int DataSensorId { get; set; }
public DateTime Time { get; set; }
public float value { get; set; }
[ForeignKey("DataSensorId")]
public virtual DataSensor datasensor { get; set; }
}
It should actually be the other way around, at least in my mind. Where the sensor would hold a List<Data> but there is no FK to link that in Sensor. A Data record is just mapped to a sensor by the FK of that sensor.
The problem I'm facing here is that I would have this line in my view now:
#model IEnumerable<DataVisualization.Models.Data>
And instead of looping over my sensors to display the information and then show the data (eventually a chart). I have to loop all the data, somehow organize it how I want and then display it. So I would still need:
#model IEnumerable<DataVisualization.Models.DataSensor>
But this does not give me access to the data since that is in Data and DataSensor does not expose any of that afaik. So I thought about somekind of class that maps them together:
public class DataViewModel
{
public DataSensor dataSensor { get; set; }
public List<GatheredData> gatheredData { get; set; }
}
And my view would require:
#model IEnumerable<DataVisualization.Models.DataViewModel>
This seemed an elegant way but I was not able to make it work. Probably since this would require public DbSet<DataViewModel> dataViewModel { get; set; } in the DbContext and that would produce a awkward table in my database.
So any help on how to create the Model, work with it in the Controller and displaying it in the View would be greatly appreciated.
Edit
What about this Model so I have access to the data connected to this?
public class DataSensor
{
[Key]
public int Id { get; set; }
public int DataNodeId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool active;
public bool alarm;
public virtual ICollection<DataSensor> Data { get; set; }
}
However, this creates a column DataSensor_Id in the database table. This obviously is undesirable since then it would be a one to one.
I am going to leave my other answer below because it still provides valuable information.
This is how you can accomplish just pulling the data from the database for display. You are using the database first approach. I would recommend creating a new project to test this and get accustomed to what is taking place. Now you are going to want to go to tools in the ribbon and select connect to database. Enter the relevant information to connect to the database. Now create new project, if you haven't already. For the purpose of learning create an Asp.Net5 web application with no authentication. Now, go to your NuGet Package Manager Console. Run " Install-Package EntityFramework.MicrosoftSqlServer -Pre ". Once that is completed Run " Install-Package EntityFramework.Commands –Pre " and " Install-Package EntityFramework.MicrosoftSqlServer.Design –Pre ". Once those are installed go to your Project.Json file and in the commands section add "ef": "EntityFramework.Commands" . Now, go to command prompt and cd to your projects directory. The easiest way I have found to do this is to right click your project and open folder in file explorer. Once you do that, go up one level so all you see is one folder. If you see all of the contents of your project then that is NOT the right place. Shift+RightClick on the folder and you should see the option to Open Command Window Here. Click that and once Command Prompt has opened in your project directory. Run
dnvm use 1.0.0-rc1-update1
or I have found that not to work in some cases. If that doesn't work then Run
dnvm use 1.0.0-rc1-final
If neither of these work you need to install 1.0.0-rc1. Once one of those work, Run
dnx ef dbcontext scaffold
"Server=EnterYourConnectionStringHere;Database=YourDataBaseNameHere;Trusted_Connection=True;"
EntityFramework.MicrosoftSqlServer --outputDir Models
Once that is complete you should have your models that were created from the database in the Models Directory. Go to your newly created Context Class in the Models Directory. Once in there you will see an override void OnConfiguring method. Delete that. Open your Startup.cs and put these using statements at the top.
using YourProject.Models;
using Microsoft.Data.Entity;
Now in your ConfigureServices Method in Startup.cs add
var connection =#"Server=YourConnectionString;Database=YourDatabaseName;Trusted_Connection=True;";
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<YourContextName>(options => options.UseSqlServer(connection));
Then the rest is just creating controllers and views for your newly registered context.
Old Answer Below
Try using the fluent API to specify the relationship. Like this: (Hint: this is in your ApplicationDbContext.)
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<DataSensor>()
.HasMany(p => p.Data)
.WithOne();
}
public DbSet<DataSensor> DataSensor { get; set; }
public DbSet<GatheredData> GatheredData { get; set; }
And your classes like this:
public class DataSensor
{
public int Id { get; set; }
public int DataNodeId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool active;
public bool alarm;
public virtual ICollection<DataSensor> Data { get; set; }
}
public class GatheredData
{
public int Id { get; set; }
public int DataSensorId { get; set; }
public DateTime Time { get; set; }
public float value { get; set; }
[ForeignKey("DataSensorId")]
public virtual DataSensor datasensor { get; set; }
}
You can also do this by convention like this
public class DataSensor
{
public int Id { get; set; }
public int DataNodeId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool active;
public bool alarm;
public virtual ICollection<DataSensor> Data { get; set; }
}
public class GatheredData
{
public int Id { get; set; }
public int DataSensorId { get; set; }
public DateTime Time { get; set; }
public float value { get; set; }
public virtual DataSensor datasensor { get; set; }
}
or by data annotations like this
public class DataSensor
{
public int Id { get; set; }
public int DataNodeId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool active;
public bool alarm;
public virtual ICollection<DataSensor> Data { get; set; }
}
public class GatheredData
{
public int Id { get; set; }
public int DataSensorId { get; set; }
public DateTime Time { get; set; }
public float value { get; set; }
[ForeignKey("DataSensorId")]
public virtual DataSensor datasensor { get; set; }
}
If you do this by convention and it doesn't work, data annotations could help map them. If all else fails then the fluent API will override everything and map the relationship.
Now, for how to display this data. If you are just trying to display the data and not edit it then I think creating a partial view will be your best bet. In your shared folder create a blank view. Add your model to the new view.
#model IEnumerable<DataVisualization.Models.GatheredData>
Then do a foreach loop to iterate through that data.
#foreach (var item in Model)
{
<p>#Html.DisplayFor(modelItem => item.Id)</p>
<p>#Html.DisplayFor(modelItem => item.Time)</p>
<p>#Html.DisplayFor(modelItem => item.Value)</p>
}
Then back in your main View for the Datasensor put the following where you want the data.
#Html.Partial("StringNameOfThePartialView", Model.Data)
The first overload is the name of the partial view, and the second is the model data to be passed to that view.
This is my first day I've spent exploring ASP.NET MVC 4. Specifically I'm using the Web API and obviously this issue is actually an MS SQL issue. I'm running EF migrations PM> Update-Database to get this error, but have seen it when first creating the models. My models are:
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public int MakeId { get; set; }
public virtual Make Make { get; set; }
public int ModelId { get; set; }
public virtual Model Model { get; set; }
}
public class Make
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Model> Models { get; set; }
}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public int MakeId { get; set; }
public virtual Make Make { get; set; }
}
The DB context is:
public class CarsContext : DbContext
{
public CarsContext() : base("name=CarsContext") { }
public DbSet<Car> Cars { get; set; }
public DbSet<Make> Makes { get; set; }
public DbSet<Model> Models { get; set; }
}
}
Would appreciate any help. My background is 5/6 solid of PHP and MySQL, so this is a steep learning curve.
Thanks.
Luke McGregor is correct. In addition to the way you fixed this you can override the default mapping that entity framework is giving you so that it doesn't cascade delete. In you CarsContext class you can override the OnModelCreating() method and specify your own mappings using fluent. This overrides what EF is trying to do by default. So you can do something like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasOptional(x => x.Model)
.WithMany(y => y.Cars) //Add this property to model to make mapping work
.WillCascadeOnDelete(false);
}
This will still work with automatic migrations.
Hope that helps.
I'm new to NHibernate and FNH as well. I'm familiar with ORM's and decided to see what all the buzz was about with this particular one mostly because of the productivity gains. At this point I think my time would have been better spent using something else but I don't want to let this defeat me and I'm hoping it's a stupid error I'm making because I want so badly for this to impress me. Anyway, I tried manually mapping the entities (Fluent) to no avail and am now trying to use the automap feature. Here is my domain model.
public class Book
{
public virtual int Id { get; protected set; }
public virtual string Isbn { get; set; }
public virtual string Title { get; set; }
public virtual string Author { get; set; }
public virtual string Publisher { get; set; }
public virtual User Seller { get; set; }
public virtual ICollection<string> Pics {get; private set;}
public virtual string Condition { get; set; }
public virtual decimal Price { get; set; }
public virtual ExchangeMethod PreferedExchangeMethod { get; set; }
}
public class Course
{
public virtual int Id { get; protected set; }
public virtual University University { get; set; }
public virtual Semester Semester { get; set; }
public virtual string Description { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Book> RequiredBooks { get; private set; }
}
public class Exchange
{
public virtual int Id { get; protected set; }
public virtual User Buyer { get; set; }
public virtual User Seller { get; set; }
public virtual Book Book { get; set; }
public virtual ExchangeMethod Method { get; set; }
public virtual decimal Price { get; set; }
public virtual int SellerFeedbackRating { get; set; }
public virtual int BuyerFeedbackRating{get; set;}
public virtual string SellerComment { get; set; }
public virtual string BuyerComment { get; set; }
}
public class Semester
{
public virtual int id { get; protected set; }
public virtual University University { get; set; }
public virtual int Year { get; set; }
public virtual string Term { get; set; }
}
public class University
{
public virtual string Name { get; set; }
public virtual int Connections { get; set; }
public virtual ICollection<Course> Courses { get; private set; }
}
public class User
{
public virtual string UserName { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Email { get; set; }
public virtual ICollection<Course> Enrollment { get; private set; }
public virtual ICollection<Book> Library { get; private set; }
public virtual ICollection<Exchange> Exchanges { get; private set; }
}
And here is my mapping
public static ISessionFactory CreateSessionFactory()
{
IPersistenceConfigurer persistenceConfigurer = SQLiteConfiguration.Standard.UsingFile(DbFile);
return Fluently.Configure()
.Database(persistenceConfigurer)
.Mappings(m => m.AutoMappings.Add(
AutoMap.AssemblyOf<User>(assem => assem.Namespace == "DataTransferObjects")
.Override<User>(map => map.Id(user => user.UserName))
.Override<University>(map => map.Id(univ => univ.Name))
))
.ExposeConfiguration(BuildSchema)
.BuildSessionFactory();
}
private static void BuildSchema(Configuration config)
{
// delete the existing db on each run
if (File.Exists(DbFile))
File.Delete(DbFile);
// this NHibernate tool takes a configuration (with mapping info in)
// and exports a database schema from it
new SchemaExport(config).Create(false, true);
}
This is the error I keep getting which is pretty vague:
XML validation error: The element 'class' in namespace 'urn:nhibernate-mapping-2.2' has invalid child element 'property' in namespace 'urn:nhibernate-mapping-2.2'. List of possible elements expected: 'meta, subselect, cache, synchronize, comment, tuplizer, id, composite-id' in namespace 'urn:nhibernate-mapping-2.2'
Thanks for any help and sorry for the long post.
EDIT
Does the way I layed out my model leave me prone to errors with circular logic e.g. User has a book and the book has a user? I would expect the automapping to be able to pick that up.
Are you using the latest version of FNH and NHibernate 2.1 RTM?
I am not so sure about your Overrides of the Id code. Have you tried without this and having an Id in each table?
Here is how I do my mappings with Automap. I tell it what Assembly to pull from, what Namespace in particular and then I give it the BaseTypes that my entities derive from.
I use the ConventionDiscovery to change some of the foreign key naming conventions and set up cascading and inverse properties, etc.
PersistenceModel = AutoPersistenceModel
.MapEntitiesFromAssemblyOf<>()
.Where(type => type.Namespace != null && type.Namespace.Contains("Model"))
.WithSetup(s =>
{
s.IsBaseType = type => type == typeof (DateTimeBase)
|| type == typeof (SimpleBase);
})
.ConventionDiscovery.AddFromAssemblyOf();
Then I add the persistence model to the automappings. I use the ExportTo method to get a copy of the generated xml files - looking at the xml helps to diagnose some problems.
.Mappings(m => m.AutoMappings
.Add(persistenceModel)
.ExportTo(#"../../ExportAutoMaps"));
AutoMapping has worked great for me - though it did take time to learn and experiment.
I am using jet database and I have to explicitly create my database file for NHibernate to run the schema on. I am unfamiliar with how sqlLite works.
Well, as I thought it was a stupid error. For the Semester Entity I had "id" instead of "Id". Kind of disapointing that FNH is case sensitive when doing the automapping, I would have thought otherwise with the "strong typing" support they have to prevent typos in config files.
FYI, When doing automapping it by default tries to map to a property called "Id" on each of your entities unless you specify other wise.