Avoiding adding a duplicate - entity-framework

I am learning EF. I have code that looks as below.
I added the Key annotations because when I add a symbol, it should be added once and no more. So if add EUR/USD, I don't want a different EUR/USD. However, on different runs of this program, when I run the code, it complains that the key is already there. How do either created the context if it is not already in the db, or get a reference to it if it already exists?
using (var db = new TickDataTestContext())
{
var td = new SymbolTickDataEntity { Symbol = symbol };
db.SymbolTickData.Add(td);
db.SaveChanges();
while (true)
{
etc....
public class SymbolTickDataEntity
{
public int SymbolTickDataEntityID { get; set; }
[Key]
[Required]
public string Symbol { get; set; }
public virtual IList<MarketDataDepthEntity> Mdds { get; set; }
public SymbolTickDataEntity() { Mdds = new List<MarketDataDepthEntity>(); }
}
public class TickDataTestContext : DbContext
{
public DbSet<MarketDataEntity> MarketData { get; set; }
public DbSet<MarketDataDepthEntity> MarketDataDepth { get; set; }
public DbSet<SymbolTickDataEntity> SymbolTickData { get; set; }
}

You could check SymbolTickData to see if that exists before inserting it.
if(!db.SymbolTickData.Any(a => a.Symbol.Equals(symbol)))
{
db.SymbolTickData.Add(td);
db.SaveChanges();
}

Related

Parent object is null when getting child objects using Entity Framework

public class AcsBatchingDbContext : DbContext
{
public DbSet<CardHolder> CardHolders { get; set; }
public DbSet<AccessCard> AccessCards { get; set; }
}
public class CardHolder
{
public int CardHolderId { get; set; }
public ICollection<AccessCard> AccessCards { get; set; };
}
public class AccessCard
{
public int AccessCardId { get; set; }
public CardHolder CardHolder { get; set; }
}
When I try to get AccessCards
using (var db = new AcsBatchingDbContext())
{
var cards = db.AccessCards.ToList();
}
Where card.CardHolder = null
Why? Why EF doesnt grab the CardHolder?
Another question:
Why this expression doesnt compile?
db.AccessCards.Include(x => x.CardHolder).ToList();
Why the only options is to use is
db.AccessCards.Include("CardHolder").ToList();
You should use include to load CardHolder entity.
(This requires using System.Data.Entity;)
db.AccessCards.Include(x => x.CardHolder).ToList();
Alternatively, you can apply Include like this;
db.AccessCards.Include("CardHolder").ToList();

Asp.net core 2.0 The instance of entity type 'X' cannot be tracked because another instance with the key value 'Id:x' is already being tracked

I have been working on a shop site project, using asp.net core spa templates provided with the latest VS2017, and have come across an issue that I haven't had before, possibly because until now my apps were quite simple!
I know what the problem is and where, I just can't fix it. I have a product model which has a collection of "Attributes" and a collection of "Variations" (different colour size, etc) and those variations also have attributes, so if the same Attribute shows up in the Variation (VAttributes), as is already in the main "Attributes" I get the error
InvalidOperationException: The instance of entity type
'ProductAttribute' cannot be tracked because another instance with the
key value 'Id:2' is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached.
The best answer I found was here : https://stackoverflow.com/a/19695833/6749293
Unfortunately, even with the above check I got the error, I even tried making a list of attached attributes, and if the vattribute matched one of the items in the list, I didn't attach it. In fact I found that even if I don't attach (_context.attach()) any of the vAttributes, it still throws the error!.
Here's the code in question:
public async Task<Product> Create(Product product)
{
try
{
foreach (var variation in product.Variations)
{
foreach (var vAttr in variation.VAttributes)
{
bool isDetached = _context.Entry(vAttr).State == EntityState.Detached;
if (isDetached)
_context.Attach(vAttr);
}
}
foreach (var attribute in product.Attributes)
{
bool isDetached = _context.Entry(attribute).State == EntityState.Detached;
if (isDetached)
_context.Attach(attribute);
}
foreach (var category in product.Categories)
{
_context.Attach(category);
_context.Attach(category).Collection(x => x.Children);
}
_context.Products.Add(product);
await Save();
return product;
}
catch (Exception)
{
throw;
}
}
The models for the 3 objects are as follows:
public class Product
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string StockRef { get; set; }
public DateTime? LastModified { get; set; }
//image needed
public ICollection<ProductCategory> Categories { get; set; }
public ICollection<ProductAttribute> Attributes { get; set; }
public ICollection<ProductVariation> Variations { get; set; }
public Product()
{
Attributes = new List<ProductAttribute>();
Variations = new List<ProductVariation>();
Categories = new List<ProductCategory>();
}
}
Variation:
public class ProductVariation
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime? LastModified { get; set; }
public virtual ICollection<ProductAttribute> VAttributes { get; set; }
//needs images
public decimal VPrice { get; set; }
public string VStockRef { get; set; }
}
Finally the Attribute:
public class ProductAttribute
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("AttributeCategory")]
public int AttributeCategoryId { get; set; }
public virtual AttributeCategory AttributeCategory { get; set; }
}
Most help I found when searching was more related to having repo's injected as singletons, or HttpPut methods where the code had check for existence omitting the .AsNoTracking() or it was a mistake that they had the second instance in some way, where I am aware of the second instance, I just don't know how to prevent it from being tracked!
EDIT: I found that adding a foreign key on the ProductVariation model to the Product that was being created failed as it was only a temp key!? anyway removed it from the variation model, so have updated my code. Also thought I'd add one of my earler failed attempts, that led to all of the foreach loops.
_context.AttachRange(product.Attributes);
_context.AttachRange(product.Categories);
_context.AttachRange(product.Variations);
_context.Add(product);
I believe you can allow EF to handle the tracking.
public virtual bool Create(T item)
{
try
{
_context.Add(item);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
This allows for you to save the entire object structure without worring about attaching items.
var newProduct = new Product();
newProduct.Categories.Add(cat);
newProduct.Attributes.Add(att);
newProduct.Variations.Add(vari);
Create(newProduct);

Should DbSets of nested classes be defined in DbContext?

Consider the following code:
public class MainObject
{
public int Id { get; set; }
List<NestedObject> NestedObjects { get; set; }
}
public class NestedObject
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ExampleContext : DbContext
{
DbSet<MainObject> MainObjects { get; set; }
// Should it be here? Code works without it...
// DbSet<NestedObject> NestedObjects { get; set; }
}
// ( ... )
var nestedFooObjects = exampleContext.Get<NestedObject>()
.Where(obj => obj.Name == "foo").ToList(); // still works
I was under the impression that a DBContext should contain all tables that are directly accessed from code. So without DbSet< NestedObject > it should only be possible to get MainObjects and then look for NestedObjects within the main ones. That is not the case apparently, as my experiments show. I want to get NestedObjects directly as in the example above. In that case: Should DbSet< NestedObject > be defined at all? Its not necessary for the code to run...

AutoMapper is Creating Duplicate Record in DB

It could be very basic question but i have stuck with this
I have an Entity Structure like this one below
public class OrderTagGroup : Entity
{
public virtual string Name { get; set; }
public virtual List<OrderTag> OrderTags { get; set; }
}
public class OrderTag : Entity
{
public virtual string Name { get; set; }
public virtual int? OrderTagGroupId { get; set; }
}
and Its respective DTO is
public class OrderTagGroupDto : EntityDto
{
[Required]
public string Name { get; set; }
public List<OrderTagDto> OrderTags { get; set; }
}
public class OrderTagDto : EntityDto
{
[Required]
public string Name { get; set; }
public int? OrderTagGroupId { get; set; }
}
I have added the Auto mapping as well like this
Mapper.CreateMap<OrderTagGroup, OrderTagGroupDto>();
Mapper.CreateMap<OrderTagGroupDto, OrderTagGroup>();
Mapper.CreateMap<OrderTag, OrderTagDto>();
Mapper.CreateMap<OrderTagDto, OrderTag>();
Here is my Repository update
public void UpdateOrderTagGroup(OrderTagGroupDto OrderTagGroup)
{
var group = _OrderTagGroupRepo.Get(OrderTagGroup.Id);
if (group != null && group.Id > 0)
{
Mapper.Map<OrderTagGroupDto, OrderTagGroup>(OrderTagGroup,group);
}
}
My Question is, whenever my OrderTagGroupDTO goes for the update, it creates a new row in the DB and previous entry is not deleted.
Could you please help me where i am wrong ?
Automapper is for entity->DTO ..
NEVER use automapper or similar for DTO->entity it's NOT designed for that
For DTO->entity you should 'always' do it 'manually'

Entity framework replaces delete+insert with an update. How to turn it off

I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}