I am new to C# and .NET, I am trying to implement CRUD functionality with local database. So I created a Product class which contains attributes and and foreign keys. But I get an error when I launch the application. The methods Update() and Delete() do not work, but Add() method works fine.
Here are my generic methods -
public class GenericRepository<T> where T : class
{
GPContext context;
IDbSet<T> dbSet;
public GenericRepository()
{
context = new GPContext();
dbSet = context.Set<T>();
}
public IEnumerable<T> getAll()
{
return dbSet.AsEnumerable();
}
public T GetById(int id)
{
return dbSet.Find(id);
}
public void Add(T t)
{
dbSet.Add(t);
context.SaveChanges();
}
public void Update(T t)
{
dbSet.Attach(t);
//on modifie la instance avant la inserer dans la bd
context.Entry(t).State = EntityState.Modified;
context.SaveChanges();
}
public void Delete(T t)
{
dbSet.Remove(t);
context.SaveChanges();
}
}
and here is how I call them in Program.cs -
GenericRepository<Product> productRepository = new GenericRepository<Product>();
Address adresse = new Address() { City = "Paris", StreetAdress = "Escalier 9" };
Category cat1 = new Category() { Name="cat1" };
Chemical p1 = new Chemical() { ProductId=26, DateProd = DateTime.Now , myAdress = adresse, Description = "CHemichal Product",
Price = 55,Name="PHARMA CHEMICH 1", Quantity = 100 , MyCategory = cat1};
Chemical p2 = new Chemical() {ProductId = 12 , DateProd = DateTime.Now , myAdress = adresse, Description = "CHemichal Update Product",
Price = 55,Name="PHARMA CHEMICH 1 Update", Quantity = 100 , MyCategory = cat1};
Product p3 = new Product() { ProductId = 11 };
productRepository.Add(p1);
productRepository.Update(p2);
productRepository.Delete(p3);
Following error occurs when I launch the application -
System.InvalidOperationException : 'A referential integrity constraint
violation occurred: The property value(s) of 'Category.CategoryId' on
one end of a relationship do not match the property value(s) of
'Product.categoryId' on the other end.'
Also I have this error in Delete method -
System.InvalidOperationException : 'The object cannot be deleted
because it was not found in the ObjectStateManager.'
The entity classes are below -
Product:
public class Product : Concept
{
[Display(Name ="Date de prod")]
[DataType(DataType.Date)]
public DateTime DateProd { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required(ErrorMessage ="Champ obligatoire")]
[StringLength(25)]
[MaxLength(50 , ErrorMessage ="taille max dans la base 50")]
public string Name { get; set; }
[DataType(DataType.Currency)]
public double Price { get; set; }
public int ProductId { get; set; }
[Range(0,int.MaxValue)]
public int Quantity { get; set; }
[DataType(DataType.ImageUrl) ,Display(Name ="Image")]
public string image { get; set; }
[ForeignKey("categoryId")]
public Category MyCategory { get; set; }
public int? categoryId { get; set; }
public List<Provider> Providers { get; set; }
public override void GetDetails()
{
Console.WriteLine("Product Name:, Price "+ Name + " " + Price);
}
public virtual void GetMyType()
{
Console.WriteLine("Uknown");
}
}
Chemical:
public class Chemical : Product
{
// public string City { get; set; }
public string LastName { get; set; }
// public string StreetAddress { get; set; }
public Address myAdress { get; set; }
public override void GetMyType()
{
System.Console.WriteLine("CHEMICAL");
}
}
Biological:
public class Biological : Product
{
public string Herbs { get; set; }
public override void GetMyType()
{
Console.WriteLine("Biological");
}
}
I am really a beginner and I have no idea about what I should do or change. I have read other answers on the same topic and I did not understand what to do.
First, you are creating 3 new entities and trying to Add, Update and Delete them. A new entity can be Added, but cannot be Updated or Deleted. Think about it, it is an object that you have just created, and it is not in the database yet. How do you expect to Update/Delete it? To Update or Delete an entity it must be an existing one.
Second, you are using newly created (without any Id) Address and Category objects to update myAdress and MyCategory properties. Those two are not primitive properties representing column values like the rest of them, they are related entities with foreign key constraints. If you really want to change/update them, you must set one that is already existing in database with Id. Otherwise, it will violate the foreign key constraint (or, referential integrity constraint), that's exactly is what the first error message is saying.
Try the following, for Update -
// retrieve an existing address from database
Address newAddress = new GenericRepository<Address>().GetById(3);
// retrieve an existing category from database
Category newCategory = new GenericRepository<Category>().GetById(5);
// retrieve an existing product from database
Chemical p2 = new GenericRepository<Product>().GetById(12);
// update/change properties, but don't change the ProductId
p2.DateProd = DateTime.Now;
p2.myAdress = newAddress;
p2.Description = "CHemichal Update Product";
p2.Price = 55;
p2.Name="PHARMA CHEMICH 1 Update";
p2.Quantity = 100;
p2.MyCategory = newCategory;
// now call for update
productRepository.Update(p2);
and for Delete -
// retrieve an existing product from database
Product p3 = productRepository.GetById(11);
// now call for delete
productRepository.Delete(p3);
Related
Currently, I am using ServiceStack.Aws v5.9.0 to communicate with DynamoDB. I have used PutItem for both creating and updating an item without anticipating data loss in case of concurrency handling.
public class Customer
{
[HashKey]
public int CustomerId { get; set; }
[AutoIncrement]
public int SubId { get; set; }
public string CustomerType { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
...//and hundreds of fields here
}
public class CustomerDynamo
{
private readonly IPocoDynamo db;
//Constructor
public CustomerDynamo()
{
var dynamoClient = new AmazonDynamoDBClient(_region);
var entityType = typeof(Customer);
var tableName = entityType.Name;
entityType.AddAttributes(new AliasAttribute(name: tableName));
db = new PocoDynamo(dynamoClient) { ConsistentRead = true }.RegisterTable(tableType: entityType);
}
public Customer Update(Customer customer)
{
customer.ModifiedDate = DateTime.UtcNow;
db.PutItem(customer);
return customer;
}
}
The above Update method is called in every service/async task that needs to update the data of the customer.
Refer to this article of AWS I decided to implement the Optimistic Locking to save my life from the issue of concurrency requests.
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBContext.VersionSupport.html
Assume that the VersionNumber will be the key for Optimistic Locking. So I added the VersionNumber into the Customer model.
public class Customer
{
[HashKey]
public int CustomerId { get; set; }
[AutoIncrement]
public int SubId { get; set; }
public string CustomerType { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
...//and hundreds of fields here
[DynamoDBVersion]
public int? VersionNumber { get; set; }
}
The result is VersionNumber not updated while it should be automatically incremented. I think it is just because the PutItem will override the whole existing item. Is this correct?
I think I need to change from PutItem to UpdateItem in the Update method. The question is how can I generate the expression dynamically to be used with the UpdateItem?
Thanks in advance for any help!
Updates:
Thanks #mythz for the useful information about DynamoDBVersion attribute. Then I tried to remove the DynamoDBVersion and using the UpdateExpression of PocoDynamo as below
public Customer Update(Customer customer)
{
customer.ModifiedDate = DateTime.UtcNow;
var expression = db.UpdateExpression<Customer>(customer.CustomerId).Set(() => customer);
expression.ExpressionAttributeNames = new Dictionary<string, string>()
{
{ "#Version", "VersionNumber" }
};
expression.ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
{
{ ":incr", new AttributeValue { N = "1" } },
{ ":zero", new AttributeValue { N = "0" } }
};
expression.UpdateExpression = "SET #Version = if_not_exists(#Version, :zero) + :incr";
if (customer.VersionNumber.HasValue)
{
expression.Condition(c => c.VersionNumber == customer.VersionNumber);
}
var success = db.UpdateItem(expression);
}
But the changes are not saved except the VersionNumber
The [DynamoDBVersion] is an AWS Object Persistence Model attribute for usage with AWS's DynamoDBContext not for PocoDynamo. i.e. the only [DynamoDB*] attributes PocoDynamo utilizes are [DynamoDBHashKey] and [DynamoDBRangeKey] all other [DynamoDB*] attributes are intended for AWS's Object Persistence Model libraries.
When needed you can access AWS's IAmazonDynamoDB with:
var db = new PocoDynamo(awsDb);
var awsDb = db.DynamoDb;
Here are docs on PocoDynamo's UpdateItem APIs that may be relevant.
I keep getting this error when I try to submit to the database:
A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'NPPRProvId'
Here's where it breaks:
public static bool Save(NPPR_Provider provider, State state, string filename, bool validateBeforeSave = true )
{
using (var db = new NPPRContext(state))
{
var prov = new NPPR_Provider()
{
First = provider.First,
Middle = provider.Middle,
Last = provider.Last,
DateOfBirth = provider.DateOfBirth,
DateOfDeath = provider.DateOfDeath,
Gender = provider.Gender,
SSN = provider.SSN,
DegreeCode = provider.DegreeCode,
BusinessName = provider.BusinessName,
DbaName = provider.DbaName,
Action = "A",
EffectiveDate = "20121212",
EndDate = "99991231",
NPPR_ServLocation = new NPPR_ServLocation()
{
EnrollmentType = provider.NPPR_ServLocation.EnrollmentType,
OrganizationType = provider.NPPR_ServLocation.OrganizationType,
ProviderTypeCode = provider.NPPR_ServLocation.ProviderTypeCode,
IRSTaxAssociations = provider.NPPR_ServLocation.IRSTaxAssociations,
NPIAssociations = provider.NPPR_ServLocation.NPIAssociations,
Address = provider.NPPR_ServLocation.Address
},
NPPR_Header = new NPPR_Header()
{
FileName = filename,
TransactionDate = Utilities.DateTimeToPRNDate(DateTime.Now),
FileLoadDate = DateTime.Now,
SubmitterId = "M00000503",
Purpose = "A",
Action = "A"
}
};
foreach(var npi in prov.NPPR_ServLocation.NPIAssociations)
{
npi.NPIType = prov.NPPR_ServLocation.OrganizationType == "I" ? "1" : "2";
}
prov.NPPR_ServLocation.Licenses = SegmentOrNull<NPPR_Licenses>(provider.NPPR_ServLocation, "Licenses", "LicenseNumber");
prov.NPPR_ServLocation.Certifications = SegmentOrNull<NPPR_Certifications>(provider.NPPR_ServLocation, "Certifications", "CertificationNumber");
prov.NPPR_ServLocation.Specialties = SegmentOrNull<NPPR_Specialties>(provider.NPPR_ServLocation, "Specialties", "SpecialtyCode");
prov.NPPR_ServLocation.Taxonomies = SegmentOrNull<NPPR_Taxonomies>(provider.NPPR_ServLocation, "Taxonomies", "TaxonomyCode");
prov.NPPR_ServLocation.OtherIds = SegmentOrNull<NPPR_OtherIds>(provider.NPPR_ServLocation, "OtherIds", "IdentifierTypeId");
prov.NPPR_ServLocation.GroupAssociations = SegmentOrNull<NPPR_GroupAssociations>(provider.NPPR_ServLocation, "GroupAssociations", "ProviderLocationId");
db.NPPR_Provider.Add(prov);
if (validateBeforeSave)
db.SaveChangesWithValidation();
else
db.SaveChanges();
return true;
}
}
db.SaveChanges() is where, according to the stacktrace, the exception is thrown. This function was originally written for EF Core, but due to issues with the server, I was forced to turn everything to EF6.4. Under Core, this method worked fine, but under EF6, it throws an exception. I've tried a few things I've read on other SO questions but so far no luck.
Specifically the exception throws on the NPPR_Provider primary key, NPPRProvId, which at no point in my code is ever read or written, except to be defined in the model.
In SQL, the NPPRProvId is PK, int, not null
The model involved:
public class NPPR_Provider : Provider
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int NPPRProvId { get; set; }
//[ForeignKey("NPPR_Header")]
public int? NPPRFileId { get; set; }
[ForeignKey("NPPRFileId")]
public NPPR_Header NPPR_Header { get; set; }
[ForeignKey("NPPRProvId")]
public NPPR_ServLocation NPPR_ServLocation { get; set; }
}
What am I doing wrong here and how might I fix it? I'm still fairly new to EF and this is my first major project in EF and MVC, and in the .NET framework in general.
In EF Core the NPPR_ServLocation could be an owned object but to my knowledge in EF 6 there are not owned objects; you need to define them explicitly in a separate table and give them keys.
public class NPPR_Provider : Provider
{
public int NPPR_ProviderId { get; set; }
public NPPR_Header NPPR_Header { get; set; }
public NPPR_ServLocation NPPR_ServLocation { get; set; }
… // data properties here
}
public class NPPR_Header {
public int NPPR_HeaderId {get;set;}
public int NPPR_ProviderId {get;set;}
public NPPR_Provider {get;set;}
… // data properties here
}
public class NPPR_ServLocation {
public int NPPR_ServLocationId {get;set;}
public int NPPR_ProviderId {get;set;}
public NPPR_Provider {get;set;}
… // data properties here.
}
I am working on a Entity Framework Code First project where I have many-to-many relationships: ServiceProvider and ServiceType
I am getting the following error when adding a new ServiceProvider into the repository: Cannot insert the value NULL into column 'ID', table 'ABCDE.dbo.ServiceTypes'; column does not allow nulls. INSERT fails.
The entity and controller classes are as follows:
ServiceProvider.cs:
public partial class ServiceProvider
{
public ServiceProvider()
{
ServiceTypes = new HashSet<ServiceType>();
}
[Key]
public int ServiceProviderID { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
public virtual ICollection<ServiceType> ServiceTypes { get; set; }
public virtual IEnumerable<Service> Services { get; set; }
}
ServiceType.cs:
public partial class ServiceType
{
public ServiceType(ServiceTypeEnum #enum)
{
ID = (int) #enum;
Name = #enum.ToString();
}
protected ServiceType() { } // For EF
//[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ID { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
public virtual ICollection<ServiceProvider> ServiceProviders { get; set; }
public static implicit operator ServiceType(ServiceTypeEnum #enum) => new ServiceType(#enum);
public static implicit operator ServiceTypeEnum(ServiceType serviceType) => (ServiceTypeEnum) serviceType.ID;
}
public enum ServiceTypeEnum
{
Ambulance = 1,
[Display(Name = "Cash Advance")]
CashAdvance = 2,
Hospitalization = 3,
Hotel = 4,
[Display(Name = "House Call")]
HouseCall = 5,
[Display(Name = "Medical Escort")]
MedicalEscort = 6,
Transfer = 7,
Repatriation = 8
}
and the ServiceProviderController.cs:
public ActionResult Create()
{
var _allServiceTypes = Enum.GetValues(typeof(ServiceTypeEnum))
.Cast<ServiceTypeEnum>()
.Select(t => new KeyValuePair<string, int>(t.ToString(), (int) t));
var viewModel = new ServiceProviderViewModel.CreateModel()
{
AllServiceTypes = _allServiceTypes
};
return View(viewModel);
}
[HttpPost]
public ActionResult Create(ServiceProviderViewModel.CreateModel viewModel)
{
if (ModelState.IsValid)
{
var serviceProvider = new ServiceProvider();
serviceProvider.Title = viewModel.Title;
repository.InsertServiceProvider(serviceProvider);
for (int i = 0; i < viewModel.SelectedServiceTypes.Length; i++)
{
var _serviceType = new ServiceType((ServiceTypeEnum)Enum.Parse(typeof(ServiceTypeEnum), viewModel.SelectedServiceTypes[i].ToString()));
serviceProvider.ServiceTypes.Add(_serviceType);
}
repository.Save();
return RedirectToAction("Index");
}
return View(viewModel);
}
I am not sure why the database table ServiceTypeServiceProviders (automatically generated by EF) is not populated with the correct data. Nothing gets stored into it. By looking at the serviceProvider object while debugging, I can see that everything seems to be fine (see the image below)
I filled the ServiceType table manually with the following data (matching the enum)
I'm assuming the "new" ServiceType object you are creating and adding to the associations for the ServiceProvider is a reference to the data you manually inserted into the ServiceType table and not a new object... If so you'll either want to retrieve the object from the DbContext rather than creating a new object or you'll have to attach the "new" ServiceType object to the current DbContext using either context.Set<ServiceType>.Attach(serviceType); or context.Entry(serviceType).State = EntityState.Attached;. These may require the assignment of the ID property for the ServiceType object to ensure it maps to the existing entities correctly. This will ensure you are not getting a null ID or duplicate entries in the ServiceType table on insert.
If this does not fix the issue, we would have to see the code for InsertServiceProvider to get a better idea of how you are adding the ServiceProvider entity to the DbContext
I'm building Backend for Mobile Application with ASP.NET MVC Framework.
I have two Objects:
public class CarLogItem : EntityData
{
public CarLogItem(): base()
{
Time = DateTime.Now;
}
public DateTime Time { get; set; }
public int RPM { get; set; }
public int Speed { get; set; }
public int RunTime { get; set; }
public int Distance { get; set; }
public int Throttle { get; set; }
[ForeignKey("Trip")]
public String Trip_id { get; set; }
// Navigation property
public TripItem Trip { get; set; }
}
and
public class TripItem : EntityData
{
public TripItem() : base()
{
UserId = User.GetUserSid();
StartTime = DateTime.Now;
logItems = new List<CarLogItem>();
}
public string UserId { get; set; }
public List<CarLogItem> logItems {get;set;}
public DateTime StartTime { get; set; }
}
and I have controller, which add new CarLogItem to database.
public class CarLogItemController : TableController<CarLogItem>
{
// POST tables/CarLogItem
public async Task<IHttpActionResult> PostCarLogItem(CarLogItem item)
{
var lastItem = db.CarLogItems.OrderByDescending(x => x.Time).FirstOrDefault();
//lastItem = (Query().Where(logitem => true).OrderBy(logitem => logitem.Time)).Last();
//checking if lastItem.Trip isn't null because
// I have entities with Trip field is null, but all of them should have it.
if (lastItem != null && lastItem.Trip != null && item.RunTime > lastItem.RunTime)
{
item.Trip = lastItem.Trip;
}
//In order to test adding of new TripItem entity to database
// I compare item.RunTime with 120, so it always true
else if (lastItem == null || item.RunTime < 120) // < lastItem.RunTime)
{
var newTrip = new TripItem();
item.Trip = newTrip;
}
else
{
throw new ArgumentException();
}
CarLogItem current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
}
When I'm trying to add new CarLogItem with Trip = null it's ok, but when Trip is particular object it fails with following Exception:
The entity submitted was invalid: Validation error on property 'Id': The Id field is required
How properly to add new CarLogItem with nested TripItem?
I think that you need to populate the Id property on your TripItem, e.g.
var newTrip = new TripItem(){ Id = Guid.NewGuid() }
You need a primary key field in every entity class, like Id or CarLogItemId (ClassName + "Id"). Or just have a property with [Key] attribute:
[Key]
public string/int/Guid/any-db-supported-type MyProp { get; set; }
Entity Framework relies on every entity having a key value that it
uses for tracking entities. One of the conventions that code first
depends on is how it implies which property is the key in each of the
code first classes. That convention is to look for a property named
“Id” or one that combines the class name and “Id”, such as “BlogId”.
The property will map to a primary key column in the database.
Please see this for more details.
I also suspect this to be a problem:
public Lazy<CarLogItem> logItems { get; set; }
You don't have to mark navigation property as Lazy<>. It is already lazy (unless you have configuration that disables lazy loading). Please try to remove Lazy<> and see if it works this way.
I'm building a feedback functionality. The feedback is supposed to have multiple categories and it should be possible to find feedback based on a category so there is a many to many relationship.
I've set up the following code for this, its designed as code first.
The feeback item:
public class FeedbackItem
{
public FeedbackItem()
{
}
[Key]
public long Id { get; set; }
public virtual ICollection<FeedbackCategory> Categorys { get; set; }
//public
public string Content { get; set; }
public bool Notify { get; set; }
public string SubscriptionUserName { get; set; }
public string SubscriptionUserEmail { get; set; }
public long SubscriptionId { get; set; }
}
The feedback category:
public class FeedbackCategory
{
[Key]
public int Id { get; set; }
public string Value { get; set; }
public virtual ICollection<FeedbackItem> Feedbacks { get; set; }
}
The Database Context:
public class FeedbackContext : DbContext, IFeedbackContext
{
public FeedbackContext() : base("DefaultConnection")
{
//Database.SetInitializer<FeedbackContext>(new FeedbackContextDbInitializer());
}
public DbSet<FeedbackItem> FeedbackItems { get; set; }
public DbSet<FeedbackCategory> Categories { get; set; }
}
And the Initializer
class FeedbackContextDbInitializer : DropCreateDatabaseAlways<FeedbackContext>
{
protected override void Seed(FeedbackContext context)
{
IList<FeedbackCategory> categories = new List<FeedbackCategory>()
{
new FeedbackCategory() { Value = "Android" },
new FeedbackCategory() { Value = "API" }
};
foreach (var feedbackCategory in categories)
{
context.Categories.Add(feedbackCategory);
}
base.Seed(context);
}
}
The code above generates three tables when ran. These being FeedbackCategories, FeedbackCategoryFeedbackItems and FeedbackItems
The table FeedbackCategories is seeded with some already existing categories. The trouble comes when I try to create a new FeedbackItem that has one or more categories.
The Json i provide is the following:
{
"categorys": [
{
"$id": "1",
"Feedbacks": [],
"Id": 1,
"Value": "Android"
}
],
"subscriptionUserName": "name",
"subscriptionUserEmail": "my#email.com",
"content": "this is a feedback item",
"notify": false,
"subscriptionId": 2
}
This is converted into a FeedbackItem and handled by the following code
public class FeedbackSqlRepository : IFeedbackSqlRepository
{
public int Create(FeedbackItem feedback)
{
if (feedback == null)
{
throw new ArgumentNullException("feedback", "FeedbackItem cannot be empty.");
}
using (var context = new FeedbackContext())
{
context.FeedbackItems.Add(feedback);
return context.SaveChanges();
}
}
}
The thing that happens here is that EF creates a new FeedbackItem, a new FeedbackCategory an maps the created feedback item to the newly created feedback category in the FeedbackCategoryFeedbackItems table.
this is not the working i want
I want the following:
Create a new FeedbackItem and reverence an existing FeedbackCategory in the FeedbackCategoryFeedbackItems table. My knowledge of EF is too little to understand what's going wrong here and what to do to get the preferred working.
========
Fixed the issue with the following code inside the Create method from the FeedbackSqlRepository:
foreach (FeedbackCategory feedbackCategory in feedback.Categories)
{
context.Entry(feedbackCategory).State = EntityState.Unchanged;
}
context.FeedbackItems.Add(feedback);
return context.SaveChanges();
Entity Framework will not examine entity contents and determine for you if they are new or added.
DbSet.Add() causes ALL entities in an object graph to be marked as added and to generate inserts when you call SaveChanges().
DbSet.Attach() leaves all entities marked as Unmodified.
If some of your entities are new, some are modified and some are just references then you should use either Add() or Attach() and then manually set entity states where necessary before calling SaveChanges().
DbContext.Entry(entity).State = EntityState.Unmodified