I write a generic repository to CRUD in EF.In add method is written like this
public class GenericRepo<T> where T : class
{
//Create
public static void Add(CellPhoneProjectEntities dbContext, T entity)
{
dbContext.Set<T>().Add(entity);
dbContext.SaveChanges();
}
}
works fine by i want to get first element of the Entity(Primary key ,Identity column )
public static long Add(CellPhoneProjectEntities dbContext, T entity,long id)
{
dbContext.Set<T>().Add(entity);
dbContext.SaveChanges();
var pk = entity.ElementType.KeyMembers[0];
//Something like first elemnt by generic
return pk as Long;
}
can anyone help to get First element(Id) of entity after insertion?
EDIT: EF database first and my primary key is not named Id..
Rather it is TableNameId eg. Table ProjectMaster has primary key ProjectMasterId
When you assume that the element's ID is a long that's the first element of they key, you're making explicitly non-generic assumptions. Not all entities have a long key, or a key at all.
If you have this assumption, it's best to have it explicitly stated in the code. Create a base entity class with an ID, and ensure all entities inherit it. That way you know each entity has a compatible ID. If your table's column is named differently, you can use the [Column] attribute to map between them:
public abstract class EntityBase
{
public virtual long Id {get; set;}
}
public class MyEntity : EntityBase
{
[Column("TableId"]
public override long Id {get;set;}
}
public long Add(DbContext context, T entity) where T : EntityBase
{
var storedEntity = dbContext.Set<T>().Add(entity);
dbContext.SaveChanges();
return storedEntity.Id; // Will always have the property.
}
After trying I find a temporary solution..but better answer is still not found ...
1 Mistake I made that not every entity (Table) contains id ..it is rather TableId
so my temporary solution is return the entity and get the value of identity column after added to db.
//Create ///Get ID of the table
public static T Add(CellPhoneProjectEntities dbContext, T entity, long id)
{
dbContext.Set<T>().Add(entity);
dbContext.SaveChanges();
return entity;
}
and to Get id
ServiceMaster master=some entity;;
long id=GenericRepo<ServiceMaster>Add(dbContext,master).ServiceMasterId;
Related
I have the following entity declared
public class TransactionEvent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public virtual List<TransactionSignInError> SignInErrors { get; set; }
}
And the context
public class TransactionAuditsDbContext : DbContext
{
public virtual DbSet<TransactionEvent> TransactionEvents { get; set; }
}
Now when I try to delete a transaction event, I want the relevant SignInError rows to be deleted as well. I realize I can do this by using cascade on delete if I had set that up in the context, too late for that now.
How can I delete successfully a transaction? I'm getting this error.
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TransactionSignInErrors_dbo.TransactionEvents_TransactionEvent_Id". The conflict occurred in database "db", table "dbo.TransactionSignInErrors", column 'TransactionEvent_Id'
I have tried clearing the SignInErrors list before deleting, that did get rid of the above error but left NULLs in the TransactionSignInErrors table.
What you want, is "Cascade on Delete": if a TransactionEvent is deleted, then you also want that all its TransactionSignInErrors are deleted.
This works on a one-to-many relation, this does not work on a many-to-many-relation.
If you have a one-to-many relation between TransactionEvents and TransactionSignInErrors, and you followed the entity framework conventions, you will have classes like
public class TransactionEvent
{
public Guid Id { get; set; }
...
// Every TransactionEvent has zero or more TransactionSignInErrors (one-to-many)
public virtual ICollection<TransactionSignInError> SignInErrors { get; set; }
}
public class TransactionSignInError
{
public Guid Id { get; set; }
...
// Every TransactionSignInError belongs to exactly oneTransactionEvent, using foreign key
public Guid TransactionEventId {get; set;}
public virtual TransactionEvent TransactionEvent { get; set; }
}
public class TransactionAuditsDbContext : DbContext
{
public DbSet<TransactionEvent> TransactionEvents { get; set; }
public DbSet<TransactionSignInError> TransactionSignInErrors {get; set;}
}
This is all that entity framework needs to know to detect the tables, the columns in the tables and the one-to-many relation between these two tables.
In entity framework the non virtual properties represent the columns in the table, the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
The foreign key TransactionEventId is a real column, hence it is non-virtual. TransactionEvent is not a real column, it only refers to the relation, hence it is declared virtual.
If you stick to the conventions, there is no need for attributes, nor fluent API. Only if you want non-default identifiers for tables, columns, column types or non-default behaviour for table relations, you might need attributes or fluent API.
Default behaviour is cascade on delete: if you delete a TransactionEvent, all its TransactioinSigninErrors are also deleted.
I'm not sure whether your problems arise because you have a GUID as primary key, instead of an int. If you want, you can inform entity framework about your one-to-many relation and cascade on delete in OnModelCreating:
protected override void OnModelCreating (DbModelBuilder modelBuilder)
{
// Every TransactionEvent has zero or more TransactionSignInErrors
// Every TransactionSignInError belongs to exactly one TransactionEvent
// using foreign key TransactionEventId.
// Also: cascade on delete:
modelBuilder.Entity<TransactionEvent>()
.HasMany(transactionEvent => transactionEvent.TransactionSignInErrors)
.WithRequired(transactionSignInError => transactionSignInError.TransactionEvent)
.HasForeignKey(transactionSignInError => transactionSignInError.TransactionEventId)
.WillCascadeOnDelete();
So three major changes to your code:
The DbSets in the DbContext are non-virtual
Added the table TransactionSignInErrors to your DbContext
If that is not enough for CascadeOnDelete (check this first!) add fluent API.
Small change: Use ICollection instead of IList.
Rationale: if you fetch a TransactionEvent with its TransactionSignInErrors, does TransactionEvent.SignInErrors[4] have a defined meaning? Wouldn't it be better if people have no access to methods that they don't know what they really mean?
If you want to use a cascade delete you have to include the children:
var removingRow=_context.Set<TransactionEvent>()
.Include(x=> x.SignInErrors )
.Where(x => x.Id ==id)
.FirstOrDefault();
if(removingRow != null)
{
_context.Remove(removingRow);
_context.SaveChanges();
}
Your post has the tag of entity-framework. I'm not sure how things work with Entity Framework 6 or previous versions, but with Entity Framework Core you can solve your issue like -
var tEvent = dbCtx.TransactionEvents
.Include(p=> p.SignInErrors)
.FirstOrDefault(p => p.Id == id);
foreach (var error in eventx.SignInErrors)
{
dbCtx.SignInErrors.Remove(error);
}
dbCtx.TransactionEvents.Remove(tEvent);
dbCtx.SaveChanges();
I am using the UnitOfWork pattern to abstract database access in my Asp.Net application. Basically I follow the UnitOfWork pattern approach described here:
https://chsakell.com/2015/02/15/asp-net-mvc-solution-architecture-best-practices/
However, I'm struggling to understand, how I will get the Id of a newly added item. Like if I want to add a new customer to my Customer repository, how will I get the customer id? The problem is that Add and Commit are decoupled, and the Id is not known until after Commit.
Is there a way to get the id of an added item, using the UnitOfWork pattern?
My approach is as follows. Simply continue working with the added entity as an object. So instead of returning it's ID, return the added object itself. Then, at some point (typically in the controller) you will call UoW.Commit(); and as a result, the Id property of the added entity will contain the updated value.
At this point, you can start using the Id property and for example store it in a cookie as you said.
Note that I dont want my EF model classes to propagate to my domain layer
I have done a workaround. I think it works pretty well
When you want a repository, for example of DbCars, and you insert a new DomainCar you want to get that Id that was only generated when SaveChanges() is applied.
public DomainCar //domain class used in my business layers
{
public int Id{get;set;}
public string Name{get;set;}
}
public DbCar //Car class to be used in the persistence layer
{
public int Id{get;set;}
public string Name{get;set;}
public DateTime CreatedDate{get;set;}
public string CreatedBy{get;set;}
}
First you create a generic IEntity interface and a class implementing it:
public interface IEntity<T>
{
T Id { get; }
}
public class Entity<T> : IEntity<T>
{
dynamic Item { get; }
string PropertyName { get; }
public Entity(dynamic element,string propertyName)
{
Item = element;
PropertyName = propertyName;
}
public T Id
{
get
{
return (T)Item.GetType().GetProperty(PropertyName).GetValue(Item, null);
}
}
}
Then in your add method of the repository you return a IEntity of the type of your Id:
public IEntity<int> AddCar(DomainCar car)
{
var carDb=Mapper.Map<DbCar>(car);//automapper from DomainCar to Car (EF model class)
var insertedItem=context.CARS.Add(carDb);
return new Entity<int>(insertedItem,nameof(carDb.Id));
}
Then , somewhere you are calling the add method and the consequent Save() in the UnitofWork:
using (var unit = UnitOfWorkFactory.Create())
{
IEntity<int> item =unit.CarsRepository.AddCar(new DomainCar ("Ferrari"));
unit.Save(); //this will call internally to context.SaveChanges()
int newId= item.Id; //you can extract the recently generated Id
}
The problem here is that the id is generated by the database, so we need to call SaveChanges so that the database generates the id and EntityFramework will fix the entity up with the generated id.
So what if we could avoid the database roundtrip?
One way to do this is to use a uuid instead of an integer as an id. This way you could simply generate a new uuid in the constructor of your domain model and you could (pretty) safely assume that it would be unique across the entire database.
Of course choosing between a uuid and an integer for the id is an entire discussion of its own: Advantages and disadvantages of GUID / UUID database keys But at least this is one point in favor of a uuid.
Unit of work should be a transaction for the entire request.
Simply just return the person id from the newly created object.
Depending on what technology you are using for your data access this will differ, but if you are using Entity Framework, you can do the following:
var person = DbContext.Set<Person>().Create();
// Do your property assignment here
DbContext.Set<Person>().Add(person);
return person.Id;
By creating the Person instance this way, you get a tracked instance that allows for lazy loading, and using the Id property, as it will be updated when SaveChanges is called (by ending your unit of work).
Instead of IDENTITY, I use SEQUENCE at database level. When a new entity is being created, first, I get the next value of the sequence and use it as Id.
Database:
CREATE SEQUENCE dbo.TestSequence
START WITH 1
INCREMENT BY 1
CREATE TABLE [dbo].[Test](
[Id] [int] NOT NULL DEFAULT (NEXT VALUE FOR dbo.TestSequence),
[Name] [nvarchar](200) NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ([Id] ASC)
)
C#:
public enum SequenceName
{
TestSequence
}
public interface IUnitOfWork : IDisposable
{
DbSet<TEntity> Set<TEntity>() where TEntity : class;
void Commit(SqlContextInfo contextInfo);
int NextSequenceValue(SequenceName sequenceName);
}
public class UnitOfWork : MyDbContext, IUnitOfWork
{
...
public void Commit(SqlContextInfo contextInfo)
{
using (var scope = Database.BeginTransaction())
{
SaveChanges();
scope.Commit();
}
}
public int NextSequenceValue(SequenceName sequenceName)
{
var result = new SqlParameter("#result", System.Data.SqlDbType.Int)
{
Direction = System.Data.ParameterDirection.Output
};
Database.ExecuteSqlCommand($"SELECT #result = (NEXT VALUE FOR [{sequenceName.ToString()}]);", result);
return (int)result.Value;
}
...
}
internal class TestRepository
{
protected readonly IUnitOfWork UnitOfWork;
private readonly DbSet<Test> _tests;
public TestRepository(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
_tests = UnitOfWork.Set<Test>();
}
public int CreateTestEntity(NewTest test)
{
var newTest = new Test
{
Id = UnitOfWork.NextSequenceValue(SequenceName.TestSequence),
Name = test.Name
};
_tests.Add(newTest);
return newTest.Id;
}
}
I don't think there is a way to do that unless you break the pattern and pass in some extra information about the newly created entity.
Since the Id will only be allocated since commit is successful and if you don't have information about which entities were created/updated/deleted, its almost impossible to know.
I once did it using the code below (I don't recommend it though but I use it for this need specifically)
public string Insert(Person entity)
{
uow.Repository.Add(entity); //public Repository object in unit of work which might be wrong
Response responseObject = uow.Save();
string id = entity.Id; //gives the newly generated Id
return id;
}
My solution is returning Lazy<MyModel> by repository method:
public class MyRepository
{
// ----
public Lazy<MyModel> Insert(MyModel model)
{
MyEntity entity = _mapper.MapModel(model);
_dbContext.Insert(entity);
return Lazy<MyModel>(()=>_mapper.MapEntity(entity));
}
}
And in the domain:
Lazy<MyModel> mayModel = unitOfWork.MyRepository.Insert(model);
unitOfWork.Save();
MyModel myModel = myModel.Value;
To expand on Martin Fletcher his answer:
EF Core generates (depending on the db provider) a temporary value for the generated id, once you add the entity to the DbContext, and start tracking it. So before the unit of work is actually committed via SaveChanges(). After SaveChanges() is called, the DbContext will actually fix up all placeholder ids with the actual generated id.
A nice example (which I quote from this answer):
var x = new MyEntity(); // x.Id = 0
dbContext.Add(x); // x.Id = -2147482624 <-- EF Core generated id
var y = new MyOtherEntity(); // y.Id = 0
dbContext.Add(y); // y.Id = -2147482623 <-- EF Core generated id
y.MyEntityId = x.Id; // y.MyEntityId = -2147482624
dbContext.SaveChangesAsync();
Debug.WriteLine(x.Id); // 1261 <- EF Core replaced temp id with "real" id
Debug.WriteLine(y.MyEntityId); // 1261 <- reference also adjusted by EF Core
More information:
https://learn.microsoft.com/en-us/ef/core/change-tracking/explicit-tracking#generated-key-values
https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations#primary-keys
Another option is to generate the keys on the client side (this is possible with for example UUID-based primary keys). But this has been discussed in other answers.
I am having an issue with performing a .find on an entity with a single PK.
The base class is as follows;
public abstract class Entity : IEntity
{
public int Id { get; set; }
public bool? IsArchived { get; set; }
}
and the class is
public class Manufacturer : Entity
{
public string Name { get; set; }
}
however, when i attempt a HTTPPUT on the Manufacturer i get the following error;
The number of primary key values passed must match number of primary key values defined on the entity.
Parameter name: keyValues
This is thrown when I call the .find method on the set;
public T Find<T>(int id, params Expression<Func<T, object>>[] includes) where T : class, IEntity
{
return this.Set<T>().Find(id, includes);
}
Any ideas why this is occurring?
Update
I am running Code first. The database has generated (this is through VS table design) PK_dbo.Manufacturers (Primary Key, Clustered: Id)
I am operating a SOLID architecture, where my controller is as follows;
[HttpPut]
[Route("")]
public Manufacturer Put(Manufacturer m)
{
var response = ManufactureService.UpdateManufacturer(m);
return response.Result;
}
where the ManufacturerService is as follows;
public ServiceResponse<Manufacturer> UpdateManufacturer(Manufacturer obj)
{
Func<Manufacturer> func = delegate
{
using (var context = _contextFactory())
{
var manufacturer = context.Find<Manufacturer>(obj.Id);
manufacturer.Name = obj.Name;
manufacturer.IsArchived = manufacturer.IsArchived;
context.Update(manufacturer);
return manufacturer;
}
};
return this.Execute(func);
}
where obj is the passed Manufacturer to be updated. and the id is an integer.
to give a little more info, I have a contect which then uses the following;
public T Find<T>(int id, params Expression<Func<T, object>>[] includes) where T : class, IEntity
{
return this.Set<T>().Find(id, includes);
}
You pass two key values to Find but EF would only deduce that Id is a PK. Do you really want a nullable bool as part of your PK? I'm not sure if this is allowed. If you do want the nullable bool as part of your key then I think you will need to let EF know about it.
If its failing on Manufacturer, and the only column is Name, then it makes zero sense that your call stack is blaming Find(int id). There is no integer primary key on this table. Maybe that is your problem.
UPDATE
Please try copying the code in the Entity class into Manufacturer. Then remove the subclass relationship. Then see if your put still fails.
I have a "base" entity with some properties that's being used by a bunch of other stuff (repository patterns, queues, etc) in some shared libraries. Mapped to a pluralized table.
I need to add a property to it for my specific implementation, and I want to reuse all the rest of the normal behaviors.
I derive a class:
public interface IItem {
[Key]
Guid Id { get; set; }
string Name { get; set; }
}
public class Item : IItem {
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
}
public interface IExtended {
bool IsExtended { get; set; }
}
[Table("Items")] // <-- my nemesis
public class ExtendedItem : Item, IExtended {
[Column("_Extended")]
public bool IsExtended { get; set; }
}
I set up the code-first context:
public class MyContext : DbContext {
public MyContext(string connectionString) : base(connectionString) {
// manually creating the tables, no migrations
Database.SetInitializer<EfQueueContext>(null);
}
public DbSet<Item> Items { get; set; }
}
Without DataAnnotation [Table] I get exception "Invalid column name 'Discriminator'" -- okay, weird but makes sense
With [NotMapped] I get exception "The entity type ExtendedItem is not part of the model for the current context" -- okay, makes sense
With annotation [Table("Item"] I get exception "table 'dbo.Item' doesn't exist" -- okay, duh forgot it pluralized original
With annotation [Table("Items")] I get exception "table 'dbo.Items1' doesn't exist" -- what??? where did the 1 suffix come from?
Even creating a brand-new DbContext that only refers to ExtendedItem and not Item still adds the '1' (update - I didn't actually create a clean instance; see comment on answer)
The problem is EF deduce you want to create a Table per Type (TPT) when you apply the Table atribute on the subclasses to specify the mapped table name. In your case you are trying to rename the table related with theExtendedItem entity, but you are using the same name of the root table, that's way EF is creating two tables one called Items1 (because you already use the Items name in the inheriting entity) related to the Item entity and another called Items related to the ExtentedItem entity
If you want to create a Table per Hierarchy (TPH) and you want to rename the root table, then you need to apply the [Table("Items")] data annotation on the root entity (Item) and all entities that inherit from it.
I have an application with 4 layers:
-Core (Models)
-Repository (EF DbContext actions)
-Service (Business logic)
-Web (MVC)
I'm trying to update an object with a 1:1 relationship with EF using the following method:
public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
{
var product = await GetProductByIdAsync(ticketing.ProductId);
// Validation removed for simplicity
// 'ticketing' passed validation so let's
// just replace it with the existing record.
product.Ticketing = ticketing;
_repo.ProductRepository.Update(product);
return await _repo.SaveAsync();
}
This works for an initial insert, but it doesn't work as I'd expect when I'm updating the record:
A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred...
The actual error message is:
Violation of PRIMARY KEY constraint 'PK_dbo.ProductTicketing'. Cannot insert duplicate key in object 'dbo.ProductTicketing'. The statement has been terminated.
Obviously the PK and FK "ProductId" doesn't change - so why does EF try to drop and insert my record instead of just updating it, and why does it fail?
But more importantly - how can I prevent this. I know I can manually map the object values and then update it - that works but it's tedious mapping two identical objects together and doesn't feel correct.
My repository for retrieving the Product object is in my Repository layer, while the method above is in my Service layer.
This is how I'm currently resolving this - and it looks as dirty as it feels:
public async Task<bool> UpdateProductTicketing(ProductTicketing ticketing)
{
var product = await GetProductByIdAsync(ticketing.ProductId);
// Validation removed for simplicity
if (product.Ticketing == null)
{
product.Ticketing = ticketing;
}
else
{
product.Ticketing.AllowEventBooking = ticketing.AllowEventBooking;
// Doing the same for all other properties etc
// etc
// etc
}
_repo.ProductRepository.Update(product);
return await _repo.SaveAsync();
}
How can I achieve this without doing all this horrible mapping an object to an identical object?
Edit
Here are the two models referred to above:
[Table(#"Products")]
public class Product
{
[Key]
public int Id { get; set; }
public virtual ProductTicketing Ticketing { get; set; }
// Removed others for clarity
[Timestamp]
public byte[] RowVersion { get; set; }
}
[Table(#"ProductTicketing")]
public class ProductTicketing
{
[Key, ForeignKey("Product")]
public int ProductId { get; set; }
public bool AllowEventBooking { get; set; }
// Removed others for clarity
public virtual Product Product { get; set; }
}
It's also probably worth noting that the "ProductTicketing" object I'm passing into the UpdateProductTicketing method is a new object created from values in my controller - but the ID is the same so I assume it should work.
I think I see the problem now - when you do product.Ticketing = ticketing;, EF treats this as a new insert.
To avoid this, you can do one of these things:
Continue using the workaround (which is not a wokaround actually but just the way EF expects you to tell when to insert vs. when to update).
Now this depends on rest of your code and design, but instead of fetching the product, you can fetch the ticket and update its properties. Of course, this means that if the ticketing is not found, you need to insert it which then kinda looks like what you're already doing with UpdateProductTicketing.
Use the InsertOrUpdate pattern (I made some assumptions about your code but hopefully it gives you the idea - the main thing here is the InsertOrUpdate method):
public class ProductRepository : IRepository
{
private SomeContext context;
public void InsertOrUpdate(ProductTicketing ticketing)
{
context.Entry(ticketing).State = ticketing.ProductId == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
}
// And a generic version
public void InsertOrUpdate<T>(T entity) where T : class
{
if (context.Entry(entity).State == EntityState.Detached)
context.Set<T>().Add(entity);
context.SaveChanges();
}
You are getting that error because ef thinks that the ProductTicket is a new entity and is trying to insert the entity into the db. I don't know about the _repo.ProductRepository.Update(product) call but how about you attach the ProductTicket to the context and set the entity state to modified