How to set up JSON columns with Npgsql? - c#

I'm setting up a mini model and I'm getting this exception when executing the code from the docs website.
Here's my code:
public class SomeEntity
{
public int Id { get; set; }
[Column(TypeName = "jsonb")]
public Customer Customer { get; set; }
}
public class Customer // Mapped to a JSON column in the table
{
public string Name { get; set; }
public int Age { get; set; }
public Order[] Orders { get; set; }
}
public class Order // Part of the JSON column
{
public decimal Price { get; set; }
public string ShippingAddress { get; set; }
}
using (var dbContext = services.GetRequiredService<AppDbContext>())
{
await dbContext.Database.MigrateAsync();
dbContext.SomeEntities.Add(
new SomeEntity
{
Customer = new Customer
{
Name = "Roji",
Age = 35,
Orders = new[]
{
new Order { Price = 3, ShippingAddress = "Somewhere" },
new Order { Price = 3, ShippingAddress = "Nowhere" }
}
}
});
await dbContext.SaveChangesAsync();
}
When I call SaveChanges, I get the following exception:
Npgsql.PostgresException:
42P01: relation \"SomeEntities\" does not exist
Here's a repro project.
Since I believe I followed all the steps in the manual, I've opened an issue here too.

You're calling the MigrateAsync method, but your project doesn't have any actual migrations (those can be created with dotnet ef migrations add <name>). If you're just playing around, you likely want to call dbContext.Database.EnsureCreated instead, see this doc page.

Related

Handling Nested Objects in Entity Framework

I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?

How to implement method of add related data in EF core?

How to implement a method of adding related data in EF core? What objects need to be submitted to the add method and what should it return? I need of related data add in DB, example:
{
"productId": 0,
"number": "xxx",
"amount": 5.65,
"primeCost": 20.33,
"productTypeId": 0,
"parameters": [
{
"id": 0,
"name": "Type",
"value": null
},
{
"id": 3,
"name": "steel grade",
"value": "CK45"
},
{
"id": 4,
"name": "diameter",
"value": "40"
}
]
}
These are my model classes:
public class Product //: BaseObject
{
public int Id { get; set; }
public string Name { get; set; }
public string Number { get; set; }
public double Amount { get; set; }
public double PrimeCost { get; set; }
[ForeignKey("ProductTypeId")]
public int ProductTypeId { get; set; }
public virtual ProductType ProductType { get; set; }
public ICollection<ProductParameter> ProductParameters { get; set; } = new List<ProductParameter>();
}
public class ProductType //: BaseObject
{
public int Id { get; set; }
public string NameType { get; set; }
public ICollection<Parameter> Parameters { get; set; } = new List<Parameter>();
public ICollection<Product> Products { get; set; } = new List<Product>();
}
public class Parameter //: BaseObject
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("ProductTypeId")]
public int ProductTypeId { get; set; }
public ProductType ProductType { get; set; }
public ICollection<ProductParameter> ProductParameters { get; set; } = new List<ProductParameter>();
}
public class ProductParameter //: BaseObject
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int ParameterId { get; set; }
public virtual Parameter Parameter { get; set; }
public string Value { get; set; }
}
These are my DTO classes:
public class ProductDTO
{
public int ProductId { get; set; }
public string Number { get; set; }
public double Amount { get; set; }
public double PrimeCostEUR { get; set; }
public int ProductTypeId { get; set; }
public string NameType { get; set; }
public ICollection<ParameterDTO> Parameters { get; set; } = new List<ParameterDTO>();
}
public class ParameterDTO
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
My implementation of method of add related data in DB:
public async Task<IEnumerable<ProductDTO>> AddProducts(ProductDTO ProductDTO,
List<ParameterDTO> ParameterDTO)
{
var EntryProduct = await _context.Products.FindAsync(ProductDTO.ProductId);
if (EntryProduct == null)
{
_context.Products.Add(new Product
{
Id = ProductDTO.ProductId,
Number = ProductDTO.Number,
Amount = ProductDTO.Amount,
PrimeCostEUR = ProductDTO.PrimeCostEUR,
});
_context.SaveChanges();
foreach (var i in ParameterDTO)
{
var EntryParameter = await _context.Parameters.FindAsync(i.Id);
if (EntryParameter != null)
{
_context.ProductParameters.Add(
new ProductParameter
{
ProductId = ProductDTO.ProductId,
ParameterId = i.Id,
Value = i.Value
});
_context.SaveChanges();
}
}
}
return ProductDTO;
}
I am getting the following exception a compiler error:
Severity Code Description Project File Line Suppression State
Error CS0266 Cannot implicitly convert type 'GdmStore.DTO.ProductDTO' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?)
You method is expecting to return an IEnumerable but you are returning just the single Product DTO that was passed in.
The signature should be:
public async Task<ProductDTO> AddProducts(ProductDTO ProductDTO, List<ParameterDTO> ParameterDTO)
Given ProductDTO has a collection of ParameterDTO, is the second argument still needed? (Looks like it would send the parameters twice)
With your entity definitions I see a few problems:
[ForeignKey("ProductTypeId")]
public int ProductTypeId { get; set; }
public virtual ProductType ProductType { get; set; }
should be
[ForeignKey("ProductType")] // FK to the reference property.
public int ProductTypeId { get; set; }
public virtual ProductType ProductType { get; set; }
All navigation properties such as the collections, and producttype should be declared as virtual otherwise you'll get inconsistent behaviour. Ones declared virtual will have access to lazy-loading if needed, the others will be left as #null.
Both Product and Parameter should not have references to ProductType, from what I can see it probably should just be on Product to avoid denormalization issues. (Product with Parameters with different ProductTypes set.)
When dealing with navigation properties I recommend removing the FK property from the entity and using mapping (EF6) / shadow properties. (EF Core)
For example:
public class ProductParameter
{
public int Id { get; set; }
public virtual Product Product { get; set; } // No ProductId/ParameterId
public virtual Parameter Parameter { get; set; }
public string Value { get; set; }
}
modelBuilder.Entity<Product>()
.HasMany(x => x.ProductParameters)
.WithOne(x => x.Product)
.HasForeignKey("ProductId"); // Sets up a shadow property.
The issue with mapping FKs is that then there are 2 sources of truth for the reference. ProductParameter.ProductId vs. ProductParameter.Product.Id. Normally these will point to the same value, but code may be dependent on one path vs. the other and lead to consistency bugs if one is changed without the other.
Use async operations sparingly. If you're pulling back a single record by ID, or any other relatively fast operation, don't use async as there is a performance cost with registering the continuation. (Faster to do just a synchronous call) Async is geared towards operations that are expected to take a while. (I.e. more than a second)
Lastly, the code may work, but it's not leveraging EF very well, setting all of these entities individually, and you generally do not want to call SaveChanges multiple times to ensure that the data is committed all together or none at all if there is an issue.
var EntryProduct = _context.Products.Find(ProductDTO.ProductId);
if (EntryProduct != null)
return ProductDTO;
var product = new Product
{
Id = ProductDTO.ProductId,
Number = ProductDTO.Number,
Amount = ProductDTO.Amount,
PrimeCostEUR = ProductDTO.PrimeCostEUR,
};
var parameterIds = ParameterDTO.Select(x => x.Id).ToList();
var parametersToAdd = context.Parameters
.Where(x => parameterIds.Contains(x.ParameterId))
.Select(x => new ProductParameter
{
Product = product,
Parameter = x
}).ToList();
product.ProductParameters.AddRange(parametersToAdd);
await _context.SaveChangesAsync();
return ProductDTO;
I don't recommend using a module level variable for the DbContext (_context) as the context should be short-lived to help avoid potential issues where one workflow intends to save, while other code may not. If it's injected by an IoC container and scoped to a lifetime matching the request then that shouldn't cause any issues. Just be cautious of contexts being left open longer than needed.

Entity Framework Code First Seeding Data

I am using code first Approach in entity framework, but I am unable to seed the default data into the table. Please help.
Models
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public int Salary { get; set; }
public virtual Department Departments { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
public Department()
{
this.Employees = new List<Employee>();
}
}
Initializer
public class DepartmentInitializer : DropCreateDatabaseIfModelChanges<EmployeeDBContext>
{
protected override void Seed(EmployeeDBContext context)
{
IList<Department> lst = new List<Department>
{
new Department
{
Name = "Developer",
Location = "Bangalore"
},
new Department
{
Name = "Tester",
Location = "Bangalore"
},
new Department
{
Name = "IT Services",
Location = "Chennai"
}
};
foreach (var item in lst)
{
context.Departments.Add(item);
}
context.SaveChanges();
}
}
Main App
class Program
{
static void Main(string[] args)
{
using (var db = new EmployeeDBContext())
{
Database.SetInitializer<EmployeeDBContext>(new DepartmentInitializer());
}
}
}
For version 6 of Entity Framework, using 'migrations' is the preferred way to version the database, using the "Configuration.Seed" method as shown in this tutorial:
http://www.asp.net/web-api/overview/data/using-web-api-with-entity-framework/part-3
Have you tried running "Update-Database" from the Package Manager Console to get it to work?
I know I have had issues using the older seeding method with EF6. Migrations has also changed for Entity Framework Core 1 (formerly EF7), so make sure you are applying the correct technique to the correct version.
Try actually querying your db
On my machine, the seeder runs when I query it for the first time.
using (var db = new EmployeeDBContext())
{
Database.SetInitializer<EmployeeDBContext>(new DepartmentInitializer());
var depts = db.Departments.ToList();
}

How do I update a record that has a child entity?

I am trying to update a record and its child at the same time. When I create the object from the database the child property is null (the property is a generic list).
I want to update the class and also update the child class without creating duplicated records in the system.
Here is how I generate the object:
var r = db.SupplierAs.Where(o => o.id == 1).First();
The SupplierA class has a property List. Using the above line of code this comes back null. I have been trying work out the code to initialize this property so I can update it but I am having no joy.
This is the original item I created:
db.Products.Add(new Product
{
name = "product test",
supplierA = new SupplierA
{
name = "supA",
price = 1.99m,
sku = "abc123",
otherCurrencies = new List<Currency>
{
new Currency
{
eur = 2.99m,
usd = 3.99m
}
}
},
});
db.SaveChanges();
I can update the supplier on its own easily like so:
var r = db.SupplierAs.Where(o => o.id == 1).First();
r.name = "Updated name";
db.SupplierAs.Attach(r);
db.Entry(r).State = EntityState.Modified;
db.SaveChanges();
But I cannot figure out how to generate the Currency object list as part of the SupplierAs object. Currencies doesnt seem to be in the db context.
Here are the class files:
public class Product
{
public int id { get; set; }
public string name { get; set; }
public virtual SupplierA supplierA { get; set; }
}
public class SupplierA
{
public int id { get; set; }
public string name { get; set; }
public string sku { get; set; }
public decimal price { get; set; }
public List<Currency> Currencies { get; set; }
}
public class Currency
{
public int id { get; set; }
public decimal eur { get; set; }
public decimal usd { get; set; }
}
The idea of products, suppliers and currencies doesn't make the greatest sense I know, I have extracted logic from my app in example, hopefully it makes enough sense what I am trying to achieve.

Saving an entity graph using entity framework

I am writing a Data Access Layer using EntityFramework 6. What I want is that when I invoke the SaveChanges() method on the DbContext, it will save the entity together with the set of relevant entities associated via navigation properties. Following is the simple code I am trying to do.
public class Customer
{
public long ID { get; set; }
public string Name { get; set; }
public virtual IEnumberable<PhoneNumber> { get; set; }
}
public class PhoneNumber
{
public long ID { get; set; }
public string Number { get; set; }
}
public class SampleContext : DbContext
{
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<PhoneNumber> PhoneNumbers { get; set; }
}
using(var context = new SampleContext())
{
var customer = new Customer { ID = 1, Name = "John" };
customer.PhoneNumbers = new PhoneNumbers[]
{
new PhoneNumber { ID = 1, Number = "1.111.1111111" },
new PhoneNumber { ID = 2, Number = "1.111.1111112" }
}
context.Customers.Add(customer);
context.SaveChanges();
}
The above code saves the customer in the customers table but saves nothing in the PhoneNumbers table.
Strange but found a solution. The above code need a little modification to make it work. Followings are the modifications:
//In Customer class, changed following line:
public virtual IEnumberable<PhoneNumber> { get; set; }
//To:
public virtual ICollection<PhoneNumber> { get; set; }
//Then in using block initialized entities as follows:
using(var context = new SampleContext())
{
var customer = new Customer { ID = 1, Name = "John", PhoneNumbers = new List<PhoneNumber>() };
customer.PhoneNumbers.Add(new PhoneNumber { ID = 1, Number = "1.111.1111111" });
context.Customers.Add(customer);
context.SaveChanges();
}

Categories