I have two entities
public class CandlestickData
{
[Key]
public int Id { get; set; }
public virtual Symbol Symbol { get; set; }
[Column(TypeName = "datetime2")]
public DateTime Time { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
}
and
public class Symbol
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
Because I add lots of 'CandlestickData' in the db per minute and the Symbols are usually the same and the don't get changed, I want to avoid unuseful calls to the DB so I made an extension method that keeps the symbols that were already retrieved from the db in a List so I re-use them.
public static class Extension
{
static List<Symbol> ExistingSymbols = new List<Symbol>();
public static Symbol GetSymbolIfExistsOrCreateItInTheDb(this string name, Repository repository)
{
if (ExistingSymbols.Any(x => x.Name == name))
{
return ExistingSymbols.First(x => x.Name == name);
}
if (repository.SymbolExists(name))
{
var symbol = repository.GetSymbol(name);
ExistingSymbols.Add(symbol);
return symbol;
}
else
{
Symbol symbol = new Symbol { Name = name };
repository.AddSymbol(symbol);
symbol = repository.GetSymbol(name);
ExistingSymbols.Add(symbol);
return symbol;
}
}
}
This is code code that adds multiple 'CandlestickData' in the db
using (var repository = new Repository())
{
var candlesticks = new List<CandlestickData>();
foreach (var symbol in AllSymbolsTradeData[GetNotUsedIndex()])
{
candlesticks.Add(new CandlestickData
{
Close = symbol.Value.Close,
Symbol = symbol.Key.GetSymbolIfExistsOrCreateItInTheDb(repository),
High = symbol.Value.High,
Low = symbol.Value.Low,
Open = symbol.Value.Open,
Time = symbol.Value.Time
});
}
repository.AddCandlesticksData(candlesticks);
repository.CommitChanges();
}
Also if you want to see how the Repository class looks:
public class Repository : IDisposable
{
private Db context;
public Repository()
{
context = new Db();
}
public bool SymbolExists(string name)
{
return context.Symbols.Where(x => x.Name == name).Any();
}
public Symbol GetSymbol(string name)
{
return context.Symbols.First(x => x.Name == name);
}
public void AddSymbol(Symbol symbol)
{
context.Symbols.Add(symbol);
context.SaveChanges();
}
public void AddCandlestickData(CandlestickData candlestickData)
{
context.Candlesticks.Add(candlestickData);
}
public void AddCandlesticksData(List<CandlestickData> candlesticks)
{
context.Candlesticks.AddRange(candlesticks);
}
public void CommitChanges()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
Now my problem is that every time my Extension method gets the symbol from the memory list(with ID and Name) after the CommitChanges method is called the reference Symbol that was used in from my list gets another ID and in the database a new Symbol is inserted with the same name but with a new ID.
My code worked correctly(without creating duplicates in the Db) when I was getting the symbols from the db every time and I can't find what I'm doing wrong since the Symbols stored in the list are the ones from the db with the correct ID...
The problem is ExistingSymbols was loaded in a different DbContext than is the context that saves the candlestick.
That is because the ExistingSymbols is static it is used for multiple operations saving candlesticks.
You might solve it by changing thr model of CandlestickData to add a foreign key:
public class CandlestickData
{
public int SymbolId { get; set; }
[ForeignKey(nameof(SymbolId))]
public virtual Symbol Symbol { get; set; }
and then use just the foreign key when creating a new object using the symbol:
candlesticks.Add(new CandlestickData
{
Close = symbol.Value.Close,
SymbolId = symbol.Key.GetSymbolIfExistsOrCreateItInTheDb(repository).Id,
High = symbol.Value.High,
Low = symbol.Value.Low,
Open = symbol.Value.Open,
Time = symbol.Value.Time
});
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 have the following scenario
Entity framework classes classes:
public class Block
{
public Guid Id { get; set; }
public ICollection<BlockLocation> BlockLocations { get; set; }
public BlockType Type { get; set; }
}
public class BlockLocation
{
public Guid Id { get; set; }
public Guid BlockId { get; set; }
public Block Block { get; set; }
}
And my Domain Entities look like
public class Block
{
public Block(BlockType type = BlockType.None) : this()
{
Type = type;
}
private Block() { }
public Guid Id { get; set; }
public List<BlockLocation> BlockLocations { get; set; }
public BlockType Type { get; set; }
}
public class LiveBlock : Block
{
public LiveBlock() : base(BlockType.Live) { }
}
public class UnsequencedBlock : Block
{
public UnsequencedBlock() : base(BlockType.Unsequenced) { }
}
public class BlockLocation
{
public Guid Id { get; set; }
public Guid BlockId { get; set; }
public Block Block { get; set; }
}
public enum BlockType
{
None = 0,
Live,
Unsequenced
}
And what I want to do is map from Entity Framework to a Domain entity to the child type and also preserve the reference so that I don't get a stack overflow
My mappings are
cfg.CreateMap<Data.Block, Domain.LiveBlock>();
cfg.CreateMap<Data.Block, Domain.UnsequencedBlock>();
cfg.CreateMap<Data.Block, Domain.Block>().PreserveReferences().ConstructUsing((block, context) =>
{
if (block.Type == BlockType.Live)
{
// This loops until stack overflow
return context.Mapper.Map<Domain.LiveBlock>(block);
}
if (block.Type == BlockType.Unsequenced)
{
return context.Mapper.Map<Domain.LiveBlock>(block);
}
return context.Mapper.Map<Domain.Block>(block);
});
cfg.CreateMap<Data.BlockLocation, Domain.BlockLocation>();
And I'm trying to do the following:
// This is the EF entity
var block = new Data.Block
{
Id = Guid.NewGuid(),
Type = BlockType.Live,
BlockLocations = new List<Data.BlockLocation>
{
new BlockLocation {Id = Guid.NewGuid()},
new BlockLocation {Id = Guid.NewGuid()}
}
};
block.BlockLocations[0].Block = block;
block.BlockLocations[1].Block = block;
// Trying to create a Domain entity
var domainBlock = Mapper.Map<Data.Block, Domain.Block>(block);
The result that I want to achieve is for domainBlock to be of type LiveBlock and have a list of BlockLocations which in turn have the same LiveBlock entity as their Block property
What I get is a loop in ConstructUsing, until I get stack overflow.
Now, my questions are:
Can this be achieved with AutoMapper?
If yes, can it be done with ContructUsing? I've also tried ConvertUsing, but I get the same result.
Some other approach maybe?
I know that a way of doing to would be to Ignore the BlockLocations property from Domain.Block and map them separately, but I would like to have Automapper to that automatically.
Thank you for your help.
Got it working with Lucian's help
I changed the mapper to the following
cfg.CreateMap<Data.Block, Domain.LiveBlock>().PreserveReferences();
cfg.CreateMap<Data.Block, Domain.UnsequencedBlock>().PreserveReferences();
cfg.CreateMap<Data.Block, Domain.Block>().PreserveReferences().ConstructUsing((block, context) =>
{
if (block.Type == BlockType.Live)
{
var b = new LiveBlock();
return context.Mapper.Map(block, b, context);
}
if (block.Type == BlockType.Unsequenced)
{
var unsequencedBlock = new UnsequencedBlock();
return context.Mapper.Map(block, unsequencedBlock, context);
}
return context.Mapper.Map<Domain.Block>(block);
});
cfg.CreateMap<Data.BlockLocation, Domain.BlockLocation>().PreserveReferences();
The secred was usint the Map method that takes the context as a parameter
context.Mapper.Map(block, unsequencedBlock, context);
I am having trouble saving children entities via Entity Framework / ASP Identity. It seems to be adding duplicates of everything that is added.
I have tried using a detached graph of the DrivingLicenceModel by TeamMember.DrivingLicence = null in the TeamMemberModel and then working with a detached graph by looking if there is new or old DrivingLicenceCategories but because DrivingLicence links back to TeamMember it causes TeamMember.DrivingLicenceId to be null as it cannot link back to TeamMember.
I have tried Manually adding the EntityState to the DrivingLicence and DrivingLicenceCategories but when I do that it complains that it cannot save two entities with the same primary key.
I assume this is because they way I am copying the entities but I after a lot of looking I am drawing a blank.
If there anyway to copy from TeamMemberRequestModel to TeamMemberModel and then save without the children trying to create clone copies of themselves?
Models
public class TeamMemberModel : IdentityUser
{
public virtual DrivingLicenceModel DrivingLicence { get; set; }
public void ShallowCopy(TeamMemberRequestModel src)
{
this.DateOfBirth = src.DateOfBirth;
if (src.DrivingLicence != null)
{
if (this.DrivingLicence == null)
{
this.DrivingLicence = new DrivingLicenceModel(src.DrivingLicence);
}
else
{
this.DrivingLicence.ShallowCopy(src.DrivingLicence);
}
}
}
public TeamMemberModel() { }
}
public class DrivingLicenceModel
{
[Key]
public int Id { get; set; }
[ForeignKey("TeamMember")]
public string TeamMemberId { get; set; }
[JsonIgnore]
public TeamMemberModel TeamMember { get; set; }
public virtual List<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
public DrivingLicenceModel() { }
public DrivingLicenceModel(DrivingLicenceModel src)
{
this.ShallowCopy(src);
}
public void ShallowCopy(DrivingLicenceModel src)
{
this.Id = src.Id;
this.IsFullLicence = src.IsFullLicence;
this.IssueDate = src.IssueDate;
this.ExpiryDate = src.ExpiryDate;
this.IssuingAuthority = src.IssuingAuthority;
this.LicenceNumber = src.LicenceNumber;
this.DrivingLicenceCategories = src.DrivingLicenceCategories;
this.DrivingLicencePoints = src.DrivingLicencePoints;
}
}
public class DrivingLicenceCategoryModel
{
[Key]
public int Id { get; set; }
[ForeignKey("DrivingLicence")]
public int DrivingLicenceId { get; set; }
[JsonIgnore]
public DrivingLicenceModel DrivingLicence { get; set; }
}
public class TeamMemberRequestModel
{
public string Id { get; set; }
public virtual DrivingLicenceModel DrivingLicence { get; set; }
}
Context
public class TIERDBContext : IdentityDbContext<TeamMemberModel, RoleModel, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
public TIERDBContext() : base("SARDBConnection") { }
public DbSet<DrivingLicenceModel> DrivingLicences { get; set; }
public DbSet<DrivingLicenceCategoryModel> DrivingLicenceCategories { get; set; }
}
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TeamMemberModel CurrentTeamMember = await this.TIERUserManager.FindByIdAsync(id);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
you have to create clone property into context class
.
In the context clases you could to use clone method that retiran the entity you send by parameters this duplicarse any entity you pass. Sorry for my english
hope you help
After far to many hours working over this. I have come to an answer. The best way to deal with this is to simply deal with it is to add or attach all entities down the tree.
The controller now attaches all children unless they have an ID of 0, therefore new and uses add instead. Then I use this very useful extension I found here http://yassershaikh.com/c-exceptby-extension-method/ to compare lists to see added and deleted entities in the list. While I don't need the added part as the entity will already be marked to an add state as I use add() it does not harm and I want to use it later with add and delete state changing.
Controller
public async Task<IHttpActionResult> Put(string id, TeamMemberRequestModel teamMember)
{
TIERDBContext IdentityContext = (TIERDBContext)this.TIERUserManager.UserStore().Context;
foreach (DrivingLicenceCategoryModel DrivingLicenceCategory in teamMember.DrivingLicence.DrivingLicenceCategories)
{
if (DrivingLicenceCategory.Id == 0)
{
IdentityContext.DrivingLicenceCategories.Add(DrivingLicenceCategory);
}
else
{
IdentityContext.DrivingLicenceCategories.Attach(DrivingLicenceCategory);
}
}
foreach (DrivingLicencePointModel DrivingLicencePoint in teamMember.DrivingLicence.DrivingLicencePoints)
{
if (DrivingLicencePoint.Id == 0)
{
IdentityContext.DrivingLicencePoints.Add(DrivingLicencePoint);
}
else
{
IdentityContext.DrivingLicencePoints.Attach(DrivingLicencePoint);
}
}
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicenceCategories.AsQueryable(),teamMember.DrivingLicence.DrivingLicenceCategories, IdentityContext);
this.DetectAddedOrRemoveAndSetEntityState(CurrentTeamMember.DrivingLicence.DrivingLicencePoints.AsQueryable(),teamMember.DrivingLicence.DrivingLicencePoints, IdentityContext);
CurrentTeamMember.ShallowCopy(teamMember);
await this.TIERUserManager.UpdateAsync(CurrentTeamMember);
}
I then use a generic that uses ExceptBy to work out what is added and delete from the old team member model to the new team member model.
protected void DetectAddedOrRemoveAndSetEntityState<T>(IQueryable<T> old, List<T> current, TIERDBContext context) where T : class, IHasIntID
{
List<T> OldList = old.ToList();
List<T> Added = current.ExceptBy(OldList, x => x.Id).ToList();
List<T> Deleted = OldList.ExceptBy(current, x => x.Id).ToList();
Added.ForEach(x => context.Entry(x).State = EntityState.Added);
Deleted.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
It works but it is far from great. It takes two DB queries, getting the original and updating. I just cannot think of any better way to do this.
Error: Violation of PRIMARY KEY constraint 'PK_dbo.CompanyDtoes'.
Cannot insert duplicate key in object 'dbo.CompanyDtoes'. The
duplicate key value is (b20a140d-440b-4a41-b2c3-6763fa752246). The
statement has been terminated.
PersonDto
public class PersonDto : PartnerDto, IPartner
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string BirthPlace { get; set; }
public string MothersName { get; set; }
public string TaxId { get; set; }
public List<CompanyDto> OwnedCompanies { get; set; }
/// <summary>
/// Partner címe(i)
/// </summary>
public List<PersonAddressDto> Addresses { get; set; }
public PersonDto()
{
OwnedCompanies = new List<CompanyDto>();
Addresses = new List<PersonAddressDto>();
}
}
CompanyDto
public class CompanyDto : PartnerDto, IPartner
{
public string TaxNumber { get; set; }
public int CompanyValue { get; set; }
public List<PersonDto> Owners { get; set; }
/// <summary>
/// Partner címe(i)
/// </summary>
public List<CompanyAddressDto> Addresses { get; set; }
public CompanyDto()
{
Owners = new List<PersonDto>();
Addresses = new List<CompanyAddressDto>();
}
}
My DBContext:
public class PartnerDBContext : DbContext
{
public DbSet<PersonDto> Persons { get; set; }
public DbSet<CompanyDto> Companies { get; set; }
public DbSet<AddressDto> Addresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonDto>()
.HasKey(k => k.PartnerId);
modelBuilder.Entity<CompanyDto>()
.HasKey(k => k.PartnerId);
modelBuilder.Entity<AddressDto>()
.HasKey(k => k.ID);
}
}
I try to insert a new person, that contains some references for already exists companies:
public bool InsertPerson(PersonDto personToInsert)
{
try
{
using (var db = new PartnerDBContext())
{
db.Persons.Add(personToInsert);
db.SaveChanges();
}
}
catch (Exception ex)
{
return false;
}
return true;
}
My problem that, I can't insert it, cos it writes Violation of primary key for CompanyDto. I know it's already exists and I don't want to add a new one, but how should I add it? I use it in a WCF service, that called from UWP. Unfortunately can't use DataAnnonations using from UWP (it's a bug), so I use ModelBuilder...
public bool InsertPerson(PersonDto personToInsert)
{
try
{
using (var db = new PartnerDBContext())
{
var companies = personToInsert.OwnedCompanies;
personToInsert.OwnedCompanies = new List<CompanyDto>();
foreach (var company in companies)
{
var companyInDb = db.Companies.Find(company.PartnerId);
personToInsert.OwnedCompanies.Add(companyInDb);
}
db.Persons.Add(personToInsert);
db.SaveChanges();
}
}
catch (Exception ex)
{
return false;
}
return true;
}
I found that solution if I get the companies from the db it saves everything as I wanted.
The problem is that when you add the person entity to the context, it also adds the related entities and marks them as Added (i.e. new). The SaveChanges() in turn tries to insert them in the database and you get the duplicate PK exception.
The solution you posted works, but involves unnecessary database trips for retrieving the related objects. Since you know they are existing, you can avoid that by simply attaching them to the context in advance, which will mark them as Unchanged (i.e. existing). Then SaveChanges will insert only the person record and the links.
using (var db = new PartnerDBContext())
{
foreach (var company in personToInsert.OwnedCompanies)
db.Companies.Attach(company);
db.Persons.Add(personToInsert);
db.SaveChanges();
}
Alternatively you can mark them as Unchanged after adding the person to the context:
using (var db = new PartnerDBContext())
{
db.Persons.Add(personToInsert);
foreach (var company in personToInsert.OwnedCompanies)
db.Entry(company).State = EntityState.Unchanged;
db.SaveChanges();
}
In my test (latest EF6.1.3, short lived new DbContext as in the posted sample) both approaches work.
I have below ItemCountAmount.cs class.
public class ItemCountAmount
{
public int Count { get; set; }
public string Amount { get; set; }
}
and I am referencing it in one more class ChartData.cs as below:
public class ChartData
{
public ChartData()
{
Purchase = Stocks = Sales = new ItemCountAmount();
}
public string period { get; set; }
public ItemCountAmount Purchase { get; set; }
public ItemCountAmount Stocks { get; set; }
public ItemCountAmount Sales { get; set; }
}
Now when I try to assign the values for the ChartData properties, it isn't recognizing or available. Compile time exception will occur in VS.
var model = new ChartData
{
period = yearMonth,
Purchase.Count = await purchase.CountAsync(x => x.order_date.Month == mnth && x.order_date.Year == yr),
}
But Purchase.Count or Purchase.Amount isn't identified here where in if I try it as
var model=new ChartData();
model.Purchase.Count = .....;
model.Purchase.Amount = .....;
.......
it works fine, I mean, property Count and Amount is identified. I am still not getting why the first method is not able to identify the properties from ItemCountAmount.cs class which is referenced in ChartData. Any specific reasons for this?
I don't believe the C# object initialiser supports that syntax, however, you can just do this. I am not sure whether this would result in two constructions and assignemnts to Purchase though, you could break point that yourself and see.
var model = new ChartData
{
Purchase = new ItemCountAmount
{
count = await purchase.CountAsync(x => x.order_date.Month == mnth && x.order_date.Year == yr)
}
}