How to use different ChangeTracker instances for each inMemory Database? - c#

I'm trying to use InMemory Database to help test out my methods.
I'm having trouble running multiple DataRows in a test because all but one will give me an error when trying to add a default record to the context. If I manually run each individually they all pass but as soon as I run the whole test they fail when all but one reach mCntx.Brokers.Add(new() ... with the following error.
System.InvalidOperationException: 'The instance of entity type 'Broker' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'
I've tried setting up a new Db name as is recommended but it still seems to draw from the same ChangeTracker.
Test Code
[TestMethod()]
[DataRow(null, _ValidBrokerNameNotInDb, _ValidBrokerageIdInDb, _ValidBrokerageNameInDb)]
[DataRow(null, _ValidBrokerNameNotInDb, null, _ValidBrokerageNameInDb)]
[DataRow(null, _ValidBrokerNameNotInDb, _ValidBrokerageIdInDb, null)]
[DataRow(null, _ValidBrokerNameNotInDb, null, _ValidBrokerageNameNotInDb)]
public void BrokerValidationTest_CreateInDb(int? brokerId, string brokerName, int? brokerageId, string brokerageName)
{
DataTable dataTable = GetDataTable(brokerId, brokerName, brokerageId, brokerageName);
List<UploadIssue> uploadIssues = new();
var rand = new Random().NextDouble();
using (SqlDboDbContext cntx = GetContext($"BrokerValidationTest_CreateInDb{rand}"))
{
InsertUpdateDatabaseService.ImportBroker(dataTable, cntx, ref uploadIssues);
cntx.ChangeTracker.Clear();
}
Assert.IsFalse(uploadIssues.Any(x => x.UploadIssueType == UploadIssueTypes.Error));
Assert.IsFalse(uploadIssues.Any(x => x.UploadIssueType == UploadIssueTypes.DependencyError));
Assert.IsFalse(uploadIssues.Any(x => x.UploadIssueType == UploadIssueTypes.Prompt));
}
public static SqlDboDbContext GetContext(string dbName)
{
var rand = new Random().NextDouble();
var options = new DbContextOptionsBuilder<SqlDboDbContext>()
.UseInMemoryDatabase(databaseName: dbName + rand.ToString())
.Options;
SqlDboDbContext mCntx = new SqlDboDbContext(options);
mCntx.Brokers.Add(new()
{
Id = _ValidBrokerIdInDb,
Name = _ValidBrokerNameInDb,
BrokerageId = _ValidBrokerageIdInDb,
Brokerage = DbBrokerage
});
mCntx.SaveChanges();
return mCntx;
}

The issue it seems to be with Brokerage = DbBrokerage
private static Brokerage DbBrokerage = new()
{
Id = _ValidBrokerageIdInDb,
Name = _ValidBrokerageNameInDb
};
Once I changed it to creating a new instance of Brokerage instead of using the static instance it started working.
From
mCntx.Brokers.Add(new()
{
Id = _ValidBrokerIdInDb,
Name = _ValidBrokerNameInDb,
BrokerageId = _ValidBrokerageIdInDb,
Brokerage = DbBrokerage
});
To
mCntx.Brokers.Add(new()
{
Id = _ValidBrokerIdInDb,
Name = _ValidBrokerNameInDb,
BrokerageId = _ValidBrokerageIdInDb,
Brokerage = new()
{
Id = _ValidBrokerageIdInDb,
Name = _ValidBrokerageNameInDb
}
});

Related

How to avoid posting duplicates into elasticsearch using Nest .NET 6.x?

When data from a device goes into the elastic there are duplicates. I like to avoid this duplicates. I'm using a object of IElasticClient, .NET and NEST to put data.
I searched for a method like ElasticClient.SetDocumentId(), but cant find.
_doc doc = (_doc)obj;
HashObject hashObject = new HashObject { DataRecordId = doc.DataRecordId, TimeStamp = doc.Timestamp };
// hashId should be the document ID.
int hashId = hashObject.GetHashCode();
ElasticClient.IndexDocumentAsync(doc);
I would like to update the data set inside the Elastic instead of adding one more same object right now.
Assuming the following set up
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool)
.DefaultIndex("example")
.DefaultTypeName("_doc");
var client = new ElasticClient(settings);
public class HashObject
{
public int DataRecordId { get; set; }
public DateTime TimeStamp { get; set; }
}
If you want to set the Id for a document explicitly on the request, you can do so with
Fluent syntax
var indexResponse = client.Index(new HashObject(), i => i.Id("your_id"));
Object initializer syntax
var indexRequest = new IndexRequest<HashObject>(new HashObject(), id: "your_id");
var indexResponse = client.Index(indexRequest);
both result in a request
PUT http://localhost:9200/example/_doc/your_id
{
"dataRecordId": 0,
"timeStamp": "0001-01-01T00:00:00"
}
As Rob pointed out in the question comments, NEST has a convention whereby it can infer the Id from the document itself, by looking for a property on the CLR POCO named Id. If it finds one, it will use that as the Id for the document. This does mean that an Id value ends up being stored in _source (and indexed, but you can disable this in the mappings), but it is useful because the Id value is automatically associated with the document and used when needed.
If HashObject is updated to have an Id value, now we can just do
Fluent syntax
var indexResponse = client.IndexDocument(new HashObject { Id = 1 });
Object initializer syntax
var indexRequest = new IndexRequest<HashObject>(new HashObject { Id = 1});
var indexResponse = client.Index(indexRequest);
which will send the request
PUT http://localhost:9200/example/_doc/1
{
"id": 1,
"dataRecordId": 0,
"timeStamp": "0001-01-01T00:00:00"
}
If your documents do not have an id field in the _source, you'll need to handle the _id values from the hits metadata from each hit yourself. For example
var searchResponse = client.Search<HashObject>(s => s
.MatchAll()
);
foreach (var hit in searchResponse.Hits)
{
var id = hit.Id;
var document = hit.Source;
// do something with them
}
Thank you very much Russ for this detailed and easy to understand description! :-)
The HashObject should be just a helper to get a unique ID from my real _doc object. Now I add a Id property to my _doc class and the rest I will show with my code below. I get now duplicates any more into the Elastic.
public void Create(object obj)
{
_doc doc = (_doc)obj;
string idAsString = doc.DataRecordId.ToString() + doc.Timestamp.ToString();
int hashId = idAsString.GetHashCode();
doc.Id = hashId;
ElasticClient.IndexDocumentAsync(doc);
}

How to identify the type of entity?

If I have a GUID for a record, but I do not know whether it is an Account or a Contact, how can I retrieve this record and identify its type?
For example, I can retrieve a specified type like so:
var serviceAppointment = organizationService.Retrieve(
"serviceappointment",
serviceActivityGuid,
new ColumnSet(true));
However, if I do not know what type of record it is, how can I retrieve it and identify its type?
Something like this:
var myEntity = organizationService.Retrieve(
"????",
myEntityGuid,
new ColumnSet(true));
If you reference the DLaB.Xrm from Nuget, you could write this like this:
bool isAccount = service.GetEntitiesById<Account>(customerId).Count == 1;
If you wanted to actually get the actual values, you could do this.
var customerId = System.Guid.NewGuid();
var entity = service.GetEntitiesById<Account>(customerId).FirstOrDefault() ??
service.GetEntitiesById<Contact>(customerId).FirstOrDefault() as Entity;
if (entity != null)
{
var account = entity as Account; // will be null if the Guid was for a contact
var contact = entity as Contact; // will be null if the Guid was for an account
}
You cannot search by the GUID alone.
You need both the GUID and the entity logical name.
Whenever you retrieve a entity reference using code, c# or javascript, the object will include the entity logical name
Update to try to find an account with a given ID:
Guid Id = // Your GUID
IOrganizationService serviceProxy = // Create your serivce proxy
var accountQuery = new QueryExpression
{
EntityName = "account",
TopCount = 1,
Criteria =
{
Conditions =
{
new ConditionExpression("accountid", ConditionOperator.Equal, Id)
}
}
}
var response = serviceProxy.RerieveMultiple(accountQuery);
if(null == response.Entities.FirstOrDefault())
{
//No Account Record Found
}
If you only need to distinguish between with Account and Contact and want to make sure the record really still exists, you could also use their CustomerAddress, LEFT OUTER JOINing both, Account and Contact:
var query = new QueryExpression
{
EntityName = "customeraddress",
ColumnSet = new ColumnSet("customeraddressid"),
TopCount = 1,
Criteria = new FilterExpression
{
Conditions =
{
// limit to Address 1
new ConditionExpression("addressnumber", ConditionOperator.Equal, 1),
// filter by "anonymous" GUID
new ConditionExpression("parentid", ConditionOperator.Equal, myEntityGuid),
},
},
LinkEntities =
{
new LinkEntity
{
EntityAlias = "acc",
Columns = new ColumnSet("name"),
LinkFromEntityName = "customeraddress",
LinkFromAttributeName = "parentid",
LinkToAttributeName = "accountid",
LinkToEntityName = "account",
JoinOperator = JoinOperator.LeftOuter
},
new LinkEntity
{
EntityAlias = "con",
Columns = new ColumnSet("fullname"),
LinkFromEntityName = "customeraddress",
LinkFromAttributeName = "parentid",
LinkToAttributeName = "contactid",
LinkToEntityName = "contact",
JoinOperator = JoinOperator.LeftOuter
},
},
};
... will allow you to retrieve whichever, Account or Contact in one go:
var customer = service.RetrieveMultiple(query).Entities.FirstOrDefault();
... but require to access their fields through AliasedValues:
string customername = (customer.GetAttributeValue<AliasedValue>("acc.name") ?? customer.GetAttributeValue<AliasedValue>("con.fullname") ?? new AliasedValue("whatever", "whatever", null)).Value as string;
... which can make reading a lot of attributes a little messy.
I know this is an old question, but I thought I would add something in case someone stumples upon this in the future with a problem similar to mine. In this case it might not be particularly the same as OP has requested.
I had to identify whether an entity was one or the other type, so I could use one method instead of having to write standalone methods for each entity. Here goes:
var reference = new EntityReference
{
Id = Guid.NewGuid(),
LogicalName = "account" //Or some other logical name - "contact" or so.
}
And by passing it into the following method the type can be identified.
public void IdentifyType(EntityReference reference)
{
switch(reference.LogicalName)
{
case Account.EntityLogicalName:
//Do something if it's an account.
Console.WriteLine($"The entity is of type {nameof(Account.EntityLogicalName)}."
case Contact.EntityLogicalName:
//Do something if it's a contact.
Console.WriteLine($"The entity is of type {nameof(Contact.EntityLogicalName)}."
default:
//Do something if neither of above returns true.
Console.WriteLine($"The entity is not of type {nameof(Account.EntityLogicalName)} or {nameof(Contact.EntityLogicalName)}."
}
}

How can I edit or add to a particular field without pull the all object

How I can do just this ( a.myFavorits.Add()) without pulling the all object to var a , because a has a lot of data, and I don't want to pull all a object, but I can't find a way do do it.
I want to do the lambada and the linq without return something but linq is always return something
public static void addFavorits(long f,long idUser)
{
using (var db = dataBase())
{
// here i pull object user from users table
var a = db.users.Where(c => c.id == idUser).SingleOrDefault();
// here i adding to the object field myFavorits new value
//myFavorits is also a table of entitys that connected to user object
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.SaveChanges();
}
}
I thought to do something like this but i dont know how to set the field users_TableId that is the key that connect the 2 tables
public static void addFavorits(long favoritId,long idUser)
{
using (var db = dataBase())
{
db.favoritsUsersLong.Add(new BE.FavoritsUsersLong {myLong = favoritId}
/*,users_TableId =idUser*/);
db.SaveChanges();
}
}
Here's a concrete example that does what you want. In this example, only the Name of a Company is modified and saved. Or an item is added to one of its collections.
var cmp = new Company{ CmpId = 1, Name = "Cmp1" }; // CmpId is the primary key
db.Companies.Attach(cmp);
db.Entry(cmp).Property(c => c.Name).IsModified = true;
// Or add an entity to a collection:
cmp.Users = new[] {new User { Name = "a1", PassWord = "a1" } };
try
{
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
finally
{
db.Configuration.ValidateOnSaveEnabled = true;
}
Result in SQL:
DECLARE #0 VarChar(30) = 'Cmp1'
DECLARE #1 Int = 1
UPDATE [dbo].[Company]
SET [Name] = #0
WHERE ([CmpId] = #1)
There are a few things to note here:
Obviously you need to know the Id of the entity you want to modify.
The object you create is called a stub entity, which is an incomplete entity. When you try to save such an entity, EF is very likely to complain about null values in required properties. That's why almost certain you'd have to disable validation (temporarily, or, better, dispose the context immediately).
If you want to add an item to a collection, you should leave validation enabled, because you'd want to know for sure that the new entity is valid. So you shouldn't mix these two ways to use a stub entity.
If you often need roughly the same small part of your entity you may consider table splitting.
I'm guessing this is what you want? I don't see you 'editting' I only see you adding.
using (var db = dataBase())
{
var a = new user();
....
//set properties etc..
...
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.users.Add(a);
db.SaveChanges();
}

ConcurrentDictionary - AddOrUpdate issue

I'm using this code below to try to update the values in a dictionary object depending on its key.
public static ConcurrentDictionary<string, SingleUserStatisticsViewModel> UsersViewModel = new ConcurrentDictionary<string, SingleUserStatisticsViewModel>();
var userSession = new UserSessionStatistic()
{
Id = "12345", Browser = "Netscape"
};
var userViewModel = new SingleUserStatisticsViewModel()
{
UserSessionStatistic = userSession,
StartTime = DateTime.Now
};
//Add first time
MyStaticClass.UsersViewModel.AddOrUpdate(userViewModel.UserSessionStatistic.Id, userViewModel, (key, model) => model);
//try to Update
var userSession2 = new UserSessionStatistic()
{
Id = "12345",
Browser = "not getting updated????"
};
var userViewModel2 = new SingleUserStatisticsViewModel()
{
UserSessionStatistic = userSession2,
StartTime = DateTime.Now
};
MyStaticClass.UsersViewModel.AddOrUpdate(userViewModel2.UserSessionStatistic.Id, userViewModel2, (key, model) => model);
But the UsersessionStatistic object in userViewModel2 is not getting updated in the ConcurrentDictionary (it's Browser propery still says "Netscape"), what am I doing wrong?
About the value factory, the docs say:
updateValueFactory Type: System.Func The
function used to generate a new value for an existing key based on the
key's existing value
Which means your passing it the existing value. You need to update it with the new one instead:
MyStaticClass.UsersViewModel.AddOrUpdate(userViewModel2.UserSessionStatistic.Id,
userViewModel2,
(key, oldModel) => userViewModel2);

EntityCommandExecutionException occures when loading embedded entity

I have a code that gets all products from my DB:
using (var entities = new DataEntities())
{
var products = entities.Products.AsQueryable();
if (!string.IsNullOrEmpty(nameFilter))
{
products = products.Where(o => o.Name.Contains(nameFilter));
}
var result = products.Select(ProductBuilder.CreateProductDto);
return result.ToList();
}
CreateProductDto method:
public static ProductDto CreateProductDto(this Product product)
{
return new ProductDto
{
Id = product.Id,
Name = product.Name,
IsEnabled = product.IsEnabled,
KeyPairDto = new KeyPairDto()
{
Id = product.KeyPair.Id,
EncryptedPrivateExponent = product.KeyPair.EncryptedPrivateExponent,
Modulus = product.KeyPair.Modulus,
PublicExponent = product.KeyPair.PublicExponent,
},
};
}
It works fine on my colleaugue's machine. But I get EntityCommandExecutionException with the folloing inner exception: There is already an open DataReader associated with this Command which must be closed first. when trying to access product.KeyPair.
Interesting thing is that if I refresh product.KeyPair via Debugger - it loads fine.
Add
MultipleActiveResultSets=true
to the provider part of the connection string within your entity framework connection string (i.e. the part the defines the data source, initial catalog, etc)

Categories

Resources