Get data from cosmos db resulted null with entity framework - c#

I have simple data in a container in cosmos db:
{
"id": "fd81aacb-64eb-452b-a0bd-c0395aa8afb6",
"thePartitionKey": "test"
}
When I try to list data with entity framework, it always returns null.
My DbContext looks something like this:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
..... //some configuration
optionsBuilder.UseCosmos(endpoint, accountKey, dbName);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Test>()
.ToContainer("theContainerID")
.HasPartitionKey(e => e.thePartitionKey);
public DbSet<Test>? Tests { get; set; }
}
The Model:
public class Test
{
public string id { get; set; }
public string thePartitionKey { get; set; }
}
The code:
public List<Test> GetDataTest(){
var qry = context.Tests.ToList();
return qry;
}
I have looked at some tutorials here, here, and here but it does not really seem to be different from my code.
What am I missing?

Using a similar example as shared by you in the question. In below screenshot you can see there is a single container in Cosmos DB having single record.
Solution:- As this container (theContainerId) stores a single document, there is no need of discriminator. In case the container has document of same entity type, we can use HasNoDiscriminator() method of Microsoft.EntityFrameworkCore package to get the record.
modelBuilder.Entity<test>()
.ToContainer("theContainerID")
.HasPartitionKey(e => e.thePartitionKey)
.HasNoDiscriminator();
Below is the screenshot of such an example.
As you can see, after modifying the modelBuilder the record is fetched.
It is explained in first link shared by you in question here at 22:16 secs.
Entity framework by default adds a discriminator value
("Discriminator" : "{modelName}" in current scenario it will be
test) while creating the object, when you have multiple entity type of documents in the same cosmos DB container.
To get more information you can refer links available under reference section.
References :-
EF Core Azure Cosmos DB Provider
Azure CosmosDB + CRUD + Entity Framework Core - FREE

Related

Is it possible to commit a read only field using EF to the database in Cosmos?

I would like to store a "calculated field" in a Cosmos document.
For example, I would like to store CostGross to be used in reporting, but EFCore should be responsible for sending the value in inserts/updates:
public class OrderItem
{
public decimal CostNet { get; set; }
public decimal CostVat { get; set; }
public decimal CostGross => CostNet + CostVat;
}
expected document in Cosmos (trimmed for brevity):
{
...
"CostNet": 1,
"CostVat": 0.2,
"CostGross": 1.2
...
}
actual document in Cosmos (trimmed for brevity):
{
...
"CostNet": 1,
"CostVat": 0.2
...
}
Is it possible to use EF to map a field so that the read only property appears in the document, meaning that the value doesn't have to be re-calculated by other clients doing a read operation?
Thanks
Yes, you can save data in any object schema. Just populate OrderItem and save it.
In Ef core 3 this is possible but you need to tweak your model builder
See this GitHub issue https://github.com/dotnet/efcore/issues/18998#issuecomment-557702703
Relevant code
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<User>()
.Property(e => e.FullName)
.UsePropertyAccessMode(PropertyAccessMode.Property); //need this line
}
That should cause EF core 3.0 to store the computed property in the database since Ef would read the property instead of the backing fields directly

Repeated Update causes tracking error

Im using VisualStudio 2017 with .Net Core 2 and EntityFrameworkCore (v2.0.1). The application is a console application which starts an MQTT Client and then processes the received messages, storing them into a database.
Each time the application starts and on the first update it works as expected. However on each subsequent update with the same data (none, one or more fields are changed), it throws an System.InvalidOperationException with the following message:
The instance of entity type 'Entity1' cannot be tracked because
another instance with the key value '[SomeKeyValue]' is already being
tracked. When attaching existing entities, ensure that only one entity
instance with a given key value is attached.
The entities are fairly simple, only using a One-To-Many relationship.
I also have the same repository used in a WebApi writing to the same database, and here the same update code works as expected without errors. I hooked up the Console App to use the WebApi instead, and this works, even though it is exactly the same Repository and Database.
I tried various recommendations I found on the internet, which is for example to explicitly detach the entity, but none of that worked.
The setup is the same as for the Asp.Net WebApi, using dependency injection
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
The One-To-Many Relations is configured like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity1>()
.HasOne<Entity2>(di => di.Entity1)
.WithMany(d => d.Entity2)
.HasForeignKey(d => d.Entity1Id);
}
The Entities are:
public class Entity1: ClientChangeTracker
{
[Key]
public string Id{ get; set; }
public ICollection<Entity2> Entity2{ get; set; }
...
}
public class Entity2: ClientChangeTracker
{
[Key]
public string Id{ get; set; }
public Entity1 Entity1{get; set; }
public string Entity1Id{ get; set; }
...
}
The repository code for adding entity:
public void AddEntity(Entity1 entity1)
{
if (_context.Entity1s.Any(x => x.Id== entity1.Id))
{
_context.Entity1s.Update(entity1).Entity;
}
else
{
_context.Entity1s.Add(entity1).Entity;
}
_context.SaveChanges();
}
Anybody any idea why this is happening and how it can be fixed?
It appears that configuring DbContext in the IoC container requires an extra step inside of a Console Application. Instead of
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
it requires an addition parameter to specify the ServiceLifetime as Transient:
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString), ServiceLifetime.Transient);
This seems to fix the problem.

Create entity context based off connection string

Objective:
Use EF to enter in data from a POST request from an API. The POST request will contain an "ID", that will map the connection string to an enum, which will have the same name connection string name as it has in the Web.config. Create the "base" context object and add the object to the appropriate table.
Note:
I know I can do this using SqlCommand, but I wanted to take a crack at it using entity framework instead, but I hit a wall.
I've used EF for years, but I wanted to make this POST method as global as I can get it. This API will accept numerous requests from numerous different web sites, but all will use the same model. Each websites POST will go into a different database (that's how they are requesting it).
The problem that I foresee is, each "entity" knows what tables it contains. So when one types context.TABLE.Add(object), EF understands that you want to put this "Car" object in the "Car" table (obviously).
Can this be done using a "global" entity???
public class DbConnectionNames
{
public enum DbConnectionStringNames
{
SocHopeHcpEntities = 1, // "1" is passed into the POST to map
XXXXEntities = 2,
......
}
}
<add name="SocHopeHcpEntities" connectionString=".........." />
<add name="XXXXEntities" connectionString=".........." />
.....
var professional = new Professional
{
....
....
};
string connStringContext = Enum.GetName(typeof(DbConnectionNames.DbConnectionStringNames), model.FormId).ToString();
string connectionString = ConfigurationManager.ConnectionStrings[connStringContext].ConnectionString;
using (var context = new ObjectContext(connectionString))
{
context.Professionals.Add(professional); // obviously this doesn't work
context.SaveChanges();
}
EDIT:
My EF is NOT using POCO, but is already based off a DB to begin with. There could be XX number of different databases, all holding the same similar table. I already have a YYYEntities.Context.cs file auto-generated that inherits from DbContext:
public partial class SocHopeHcpEntities : DbContext
{
public SocHopeHcpEntities()
: base("name=SocHopeHcpEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<AreasOfWork> AreasOfWorks { get; set; }
public virtual DbSet<Professional> Professionals { get; set; }
}
You still need a context that understands what a Professional is. For example:
public class ProfessionalContext : DbContext
{
public ProfessionalContext(string connectionString)
: base(connectionString)
{
//This line is optional but it prevents initialising the database
//every time you connect to a new database
Database.SetInitializer<ProfessionalContext>(null);
}
public DbSet<Professional> Professionals { get; set; }
}
And use it like this:
using (var context = new ProfessionalContext(connectionString))
{
context.Professionals.Add(professional);
context.SaveChanges();
}
DavidG's answer showed you how to pass a connection string to a strongly typed context that knows the entity types you are dealing with. That context inherits from DbContext which you can use directly as illustrated below.
It is worth noting the generic way that does not involve a context 'object' that is specific to your database.
For example see how ObjectContext is used here:
System.Data.Objects.ObjectContext oc = new System.Data.Objects.ObjectContext("connection string");
oc.AddObject("ProfessionalsTable", professional);
Another example is using DbContext:
System.Data.Entity.DbContext dbc = new DbContext("");
dbc.Set(typeof(Professional)).Add(professional);
The generic approach is better if you also do not know which table you want to insert to, so you can also make the object that you want to insert dynamic.

Does Linq-to-Sqlite support dynamic table creation using Code First approach?

I am trying to run a demo app to do a POC on Linq-to-Sqlite. I'm using the latest System.Data.Sqlite NuGet package and .Net Framework 4.5 to create sample console application which will only create the database file initially. The code simply creates an object of Movie class and adds it to the DbSet property on the DbContext class. However, on SaveChanges(), i get a System.Data.Entity.Infrastructure.DbUpdateException with the message "no such table: Movie". Below is my code:
if (!File.Exists(_databaseFilePath))
SQLiteConnection.CreateFile(_databaseFilePath);
using (var dbContext = new MovieAppContext())
{
dbContext.Movies.Add(new Movie(1, "Titanic", 7.5));
var retVal = dbContext.SaveChanges();
}
public class MovieAppContext : DbContext
{
public MovieAppContext():base("MovieAppConStr")
{
}
public MovieAppContext(string connectionString)
: base(connectionString)
{ }
public DbSet<Movie> Movies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Chinook Database does not pluralize table names
modelBuilder.Conventions
.Remove<PluralizingTableNameConvention>();
}
}
EDIT: So the question is, just like Linq-to-Sql, does Linq-to-Sqlite provider allow us to create tables in the database equivalent to the models created using the code-first approach ?
Entity Framework Code First with SQLite only Works with an existing database, so no. There are alternative EF providers for sqlite that do support database creation with Code First... Devart for example

Code first with the existing DB

I am fairly new to Entity Framework and MVC. I can use the Entity Data Model Wizard (EDMW) on an existing SQL server database and automatically create the necessary classes (context, models,etc). Then pass the model (db.Employees.tolist()) to a view from the controller. Everything works fine and I can see the list of all the employees on the browser from the company.employees table.
However, I want to write the code myself without the help of EDMW or other tools to better understand the entity/MVC/database interactions. So I wrote the following code but can't seem to get the table displayed on the browser. I know I am missing something.
SQL SERVER:
Schema.tablename (Acme.Employees)
public class AcmeContext : DbContext
{
public virtual DbSet<Employee> Employees { get; set; }
}
public class Employee
{
public int customerid {get; set;}
public string name {get;set;}
public string state {get;set;}
}
public ActionResult list()
{
var db = new AcmeContext();
return view(db.Employees.tolist());
}
<connectionStrings>
<add
name="AcmeContext"
connectionString="data source=localhost;initial catalog=Acme;integrated security=True;"
providerName="System.Data.SqlClient" />
</connectionStrings>
What else is needed?
Also, how is this done in the real world when the DB already exists? Does the developer use the EDM wizard or manually code everything the wizard does in Visual Studio or other IDE?
By default when you are using Entity Framework code first, the schema is dbo
you can change schema by using using fluent API or Data Anotation
Fluent API :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>().ToTable("Employees", "Acme");
}
Data Anotations
[Table("Employees", Schema = "Acme")]
public class Employee
{
...
}
You don't have a construction calling your connection string
public class AcmeContext : DbContext
{
public AcmeContext() :base(nameOrConnectionString: "AcmeContext"){}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove();
}
public virtual DbSet Employees { get; set; }
}
Your question already answered here. Please refer to this topic
Code-first vs Model/Database-first
I solved my problem.
The EF created a table dbo.employees instead of using the acme.employees. All along it was looking at the employee table in the dbo schema. Of course there is no data in that table and thus the blank web page. I added some data and now can see it on the page.
What I still don't know is the data annotation that Aiska Hendra suggested didn't work earlier.

Categories

Resources