Entity Framework - Conditionally include related entities - c#

I maintain an API that, based on a request for a list of people, returns a different result set based on the request. For example, some API clients want to get a list of people and a list of their interactions, others want people and a list of their metadata. All this can be specified int he request to the API method that returns people.
This does not appear to work:
using (var dbcontext = new ExampleEntities())
{
var query = dbcontext.People.AsQueryable();
//determined in earlier application logic based on request
if(includeMetadata)
{
query = query.Include("metadata");
}
//determined in earlier application logic based on request
if(includeInteractions)
{
query = query.Include("interactions");
}
/* ...SNIP... */
}
What I don't want to do is this:
var query = dbcontext.People.Include("Metadata").Include("interactions");
which will mean every request to get a person will include ALL their related entities, even if the requesting API client does not need them.
I also don't want to code every possible combination of logic:
if(includeMetadata && includeInteractions)
{
var query = dbcontext.People.Include("Metadata").Include("interactions");
}
else if(includeMetadata)
{
var query = dbcontext.People.Include("Metadata");
}
else if(includeInteractions)
{
var query = dbcontext.People.Include("Interactions");
}
else
{
var query = dbcontext.People;
}
This will result in hard-to-maintain code, however, I realize I could code generate this if needed.

You can chain the IQueryable's
using (var dbcontext = new ExampleEntities())
{
var query = dbcontext.People.AsQueryable();
if(includeMetadata)
{
query = query.Include("metadata");
}
if(includeInteractions)
{
query = query.Include("interactions");
}
}

Your first example should work if you replace u with query
u = u.Include("metadata");
with
query = query.Include("metadata");

Works fine here... checking the sql statements with the EF 6 Log handler
[TestClass]
public void SomeTestClass
{
[TestMethod]
public void ShouldLoadOnlyRequiredCollections()
{
Database.SetInitializer(new DropCreateDatabaseAlways<HomesContext>());
var db = new HomesContext();
Assert.IsFalse(db.Homes.Any());
var home = db.Homes.Create();
db.Homes.Add(home);
home.Staff.Add(new Staff { Name = "wilma" });
home.Staff.Add(new Staff { Name = "betty" });
home.Residents.Add(new Resident { Name = "fred" });
home.Residents.Add(new Resident { Name = "barney" });
db.SaveChanges();
db = null;
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<HomesContext>());
var sb = new StringBuilder();
db = new HomesContext();
db.Database.Log = ((s) => { sb.Append(s + "\r"); });
Assert.IsTrue(db.Homes.Any());
string log;
log = sb.ToString();
Assert.IsTrue(sb.ToString().Contains("FROM [dbo].[Homes]"));
sb = new StringBuilder(); //ok get residents
var q = db.Homes.Include("Residents");
Assert.IsTrue(string.IsNullOrEmpty(sb.ToString()));
var lst = q.ToList();
log = sb.ToString();
Assert.IsTrue(sb.ToString().Contains("[dbo].[Homes]"));
Assert.IsTrue(sb.ToString().Contains("[dbo].[Residents]"));
Assert.IsTrue(!sb.ToString().Contains("[dbo].[Staff]"));
sb = new StringBuilder(); //get staff
q = db.Homes.Include("Staff");
Assert.IsTrue(string.IsNullOrEmpty(sb.ToString()));
lst = q.ToList();
log = sb.ToString();
Assert.IsTrue(log.Contains("[dbo].[Homes]"));
Assert.IsTrue(!log.Contains("[dbo].[Residents]"));
Assert.IsTrue(log.Contains("[dbo].[Staffs"));
sb = new StringBuilder(); //get residents and staff
q = db.Homes.Include("Staff");
q = q.Include("Residents");
lst = q.ToList();
log = sb.ToString();
Assert.IsTrue(log.Contains("[dbo].[Homes]"));
Assert.IsTrue(log.Contains("[dbo].[Residents]"));
Assert.IsTrue(log.Contains("[dbo].[Staffs]"));
}
}
public class HomesContext:DbContext
{
public DbSet<Home> Homes { get; set; }
}
public class Home
{
public Home()
{
Staff = new List<Staff>();
Residents = new List<Resident>();
}
public int HomeId { get; set; }
public string HomeName { get; set; }
public int MaxResidents { get; set; }
public int MaxStaff { get; set; }
public int CurrentResidents { get; set; }
[NotMapped]
public int CurrentStaff { get; set; }
public IList<Staff> Staff { get; set; }
public IList<Resident> Residents { get; set; }
}
public class Staff
{
public int StaffId { get; set; }
public string Name { get; set; }
public int HomeId { get; set; }
public Home Home { get; set; }
}
public class Resident
{
public int ResidentId { get; set; }
public string Name { get; set; }
public int HomeId { get; set; }
public Home Home { get; set; }
}

Related

How to add to a RavenDB List<type> in .net

Update: I fixed it by copy pasting the working code into a new clean project. I have no idea why I was getting that bug, but as long as it's gone
I'm having problems figuring exactly how to do what I want.
In this instance, what I have is a db of merchants, and I want to be able to add venues as a list to the merchant entry in the database. For some reason I cannot figure out why, I can fetch the merchant, but cannot seem to get a hold of the id of the entry that I have fetched and then update it. (I'm having some problems with updating as well, From what I've seen I need the Id of the entry I want, and then I can update with a patch... right?)
Here is my Json class:
public class Merchant
{
[JsonProperty(PropertyName = "name")]
public string name { get; set; }
[JsonProperty(PropertyName = "venues")]
public List<Venue> venues { get; set; }
}
public class Venue
{
[JsonProperty(PropertyName = "name")]
public string name { get; set; }
[JsonProperty(PropertyName = "tills")]
public List<Till> tills { get; set; }
}
public class Till
{
[JsonProperty(PropertyName = "name")]
public string name { get; set; }
}
Here's my RavenDB Handler class's functions that are relevent:
public List<JObject> QueryFromDb(string query)
{
List<Object> objReturned;
List<JObject> jObjects = new List<JObject>();
using (IDocumentSession session = store.OpenSession())
{
objReturned = session
.Advanced.RawQuery<Object>(query)
.ToList();
}
for (var i = 0; i < objReturned.Count; i++)
{
var json = JsonConvert.SerializeObject(objReturned[i], Formatting.Indented);
jObjects.Add(JObject.Parse( json.ToString()));
}
return jObjects;
}
public String GetJsonFromDB(string query)
{
string returnStr = "";
List<JObject> myResponse = QueryFromDb(query);
for (var i = 0; i < myResponse.Count; i++)
{
var json = JsonConvert.SerializeObject(myResponse[i], Formatting.Indented);
returnStr += json.ToString();
}
return returnStr;
}
And here is me trying to get ahold of the Id of the ravendb entry:
public void UpdateMerchantList()
{
merchantGrid.Rows.Clear();
List<JObject> myResponse = ravenDB.QueryFromDb("from Merchants");
for (var i = 0; i < myResponse.Count; i++)
{
var json = JsonConvert.SerializeObject(myResponse[i], Formatting.Indented);
Merchant merchant = new Merchant(json.ToString());
if (myResponse[i].Property("Id") != null) { merchant.MyID = myResponse[i].Property("Id").ToString(); }
merchantGrid.Rows.Add(merchant.MyID, merchant.name);
}
}
For some reason, I took this code and transplanted it into a console app, and got it to work with this code:
List<JObject> result = ravenDb.QueryFromDb("from Merchants");
for(var i = 0; i < result.Count; i++)
{
Console.WriteLine(result[i].Property("Id").ToString());
}
Which does give me the exact stuff I want:
"Id": "merchants/97-A"
"Id": "merchants/98-A"
"Id": "merchants/129-A"
"Id": "merchants/130-A"
But when I transplant it backinto my form and try to add this to the datagridview I cannot see it anymore.
update: have been able to add to the list into the merchant class in the console app. Here is the code.
public void AddVenue(string idArg,Venue venue)
{
using (IDocumentSession session = store.OpenSession())
{
var merchant = session.Load<Merchant>(idArg);
List<Venue> venuesList = new List<Venue>();
if (merchant.venues == null) { session.Advanced.Patch(merchant, m => m.venues, venuesList); }
else
{
session.Advanced.Patch(merchant,
x => x.venues,
venues => venues.Add(venue));
}
session.SaveChanges();
}
}
Just so people understand what I'm talking about: here is the json that outputs from the form application:
Why?
when in console with pretty much identical code (copy pasted) I get this:
I'm getting more data from one application than the other, and I really really want the id.
I'm going to refactor the code into a wcf application I guess. Just because. Maybe I'll accidentally fix it doing that.
You can load directly the object of given class, no need to handle JSON (de)serialization yourself, unless there is a specific reason to do so.
Also, using patching is more useful when you don't want to load the document (to save bandwidth). When you already have the document loaded, you can simply change it and save the changes.
See the following code for reference:
public class Merchant
{
// note added Id property
public string Id { get; set; }
[JsonProperty(PropertyName = "name")]
public string name { get; set; }
[JsonProperty(PropertyName = "venues")]
public List<Venue> venues { get; set; }
}
public class Venue
{
[JsonProperty(PropertyName = "name")]
public string name { get; set; }
[JsonProperty(PropertyName = "tills")]
public List<Till> tills { get; set; }
}
public class Till
{
[JsonProperty(PropertyName = "name")]
public string name { get; set; }
}
public void MerchantsTest()
{
using (var store = GetDocumentStore())
{
using (var session = store.OpenSession())
{
session.Store(new Merchant { Id = "merchant-1", name = "merchant1", venues = new List<Venue> { new Venue { name = "venue-1A", tills = new List<Till> { new Till { name = "till-1A-first" } } } } });
session.Store(new Merchant { Id = "merchant-2", name = "merchant2", venues = new List<Venue> { new Venue { name = "venue-2A", tills = new List<Till> { new Till { name = "till-2A-first" } } } } });
session.SaveChanges();
}
using (var session = store.OpenSession())
{
// you can load all merchants
var merchants = session.Query<Merchant>(null, "Merchants").ToList();
// or load specific merchant by ID
var merchant2 = session.Load<Merchant>("merchant-1");
// add a venue to a loaded merchant (not using patch but simply adding the object)
merchant2.venues.Add(new Venue { name = "venue-2B", tills = new List<Till> { new Till { name = "till-2B-first" } } });
session.SaveChanges();
}
}
}

How to fill mongo Db table by C# Driver?

I am not an expert of nosql but a year ago I created a mongodb table by using below code:
const string connectionString = "mongodb://localhost:27017";
// Create a MongoClient object by using the connection string
var client = new MongoClient(connectionString);
////Use the MongoClient to access the server
var database = client.GetDatabase("YUSUF");
////get mongodb collection
var collection = database.GetCollection("expressions");
var expression = new Expression { Id = Guid.NewGuid().ToString(),ExpressionSentence = "Test",Name = "yusuf",CreatedDate = DateTime.Now,Status = true };
collection.InsertOneAsync(expression);
public class Expression {
[BsonId]
public string Id { get; set; }
public string Name { get; set; }
public string ExpressionSentence { get; set; }
public bool Status { get; set; }
public DateTime CreatedDate { get; set; }
}
Today above codes is doing nothing now. Not working also not throwing any error. What am I doing wrong?
static void insert()
{
var connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var database = client.GetDatabase("YUSUF");
var collection = database.GetCollection<Expression>("expressions");
var expression = new Expression { Id = Guid.NewGuid().ToString(),ExpressionSentence = "Test",Name = "yusuf",CreatedDate = DateTime.Now,Status = true };
collection.InsertOneAsync(expression);
}
public class Expression {
[BsonId]
public string Id { get; set; }
public string Name { get; set; }
public string ExpressionSentence { get; set; }
public bool Status { get; set; }
public DateTime CreatedDate { get; set; }
}
have to work with the latest C# MongoDB Driver.
MY METHOD
static void insert()
{
var connectionString = "mongodb://localhost:27017";
var client = new MongoClient(connectionString);
var database = client.GetDatabase("fairytale");
// var unicorns = database.GetCollection("unicorns");
var unicorns = database.GetCollection<BsonDocument>("unicorns");
int i = 0;
while (i < 5000)
{
var document = new BsonDocument
{
{"name",GenerateRandomUnicornName()},
{"horns",Random.Next(50)},
{"likes",new BsonArray{ "apple", "onion" }},
};
unicorns.InsertOneAsync(document);
i++;
}
}

How do I create and insert one-to-many object with entity framework c#

I'm trying to create an object and insert to the database but keep getting the same error no matter what I try.
The row that I get the error on is ColumnGroupTest.ValidValues.Add(memberComment1); the error is
error message
NullReferenceException was unhandled by user code
my models
public class StoreColumnName
{
public int Id { get; set; }
public string StoreColumnGroupName { get; set; }
public string ColumnName { get; set; }
public string ColumnType { get; set; }
public List<StoreValidValue> ValidValues { get; set; }
}
public class StoreValidValue
{
public int Id { get; set; }
public string ValidValue { get; set; }
public StoreColumnName StoreColumnName { get; set; }
}
my controller
public ActionResult Index()
{
XDocument document = XDocument.Load(#"C:\Users\Physical.xml");
var result = document.Descendants("ColumnGroup");
foreach(var item in result){
var ColumnGroupName = item.Attribute("name").Value;
var Columns = item.Descendants("Column");
foreach (var itemColumn in Columns)
{
StoreColumnName ColumnGroup = new StoreColumnName();
var ColumnGroupTest = new StoreColumnName
{
StoreColumnGroupName = ColumnGroupName,
ColumnName = itemColumn.Attribute("name").Value,
ColumnType = itemColumn.Attribute("type").Value,
Id = 11
};
var ValidValues = itemColumn.Descendants("ValidValues");
var Values = ValidValues.Descendants("Value");
foreach (var Value in Values)
{
var memberComment1 = new StoreValidValue
{
StoreColumnName = ColumnGroupTest,
ValidValue = Value.Value,
Id = 101
};
ColumnGroupTest.ValidValues.Add(memberComment1);
}
}
}
return View();
}
(I gladly take tips on what I can improve when asking for help/guiding here).
Can anyone help ?
The issue that you're having is that you don't initialize your ValidValues property to a list. By default, those types of properties initialize to null unless you specify differently.
The best approach is to add that initialization to your constructor of that object.
public StoreColumnName() {
this.ValidValues = new List<StoreValidValue>();
}

entity framework - two people with the same data

I want to map a two tables in entity framework 6 and need some help! It is for my chat application; I need to map user conversations into the database. Both group and private messages. For this question however, if you help me with the private messaging mapping, I should hopefully work out the group by myself :) anyway....
Each user can talk to any other user. They however share the same data, which is where I am struggling a bit: how to set the keys to the exact same data without duplication. This is what I have so far:
**EDIT - new code *****
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
namespace CodeFirstNewDatabaseSample
{
static void Main(string[] args)
{
using(var db = new PrivateMessageContext())
{
Console.Write("Enter message: ");
var message = Console.ReadLine();
var userFrom = "userFrom";
var userTo = "userTo";
var messageDetail = new PrivateMessageDetail(MessageDate = DateTime.Now, FromUser = userFrom, message = message);
var pm = new PrivateMessageHeader { User1 = userFrom, User2 = userTo, TimeStamp = DateTime.Now };
pm.Messages.Add(messageDetail);
db.PrivateMessages.Add(pm);
db.SaveChanges();
// Display all Blogs from the database
foreach(var pmsg in db.PrivateMessages)
{
var query = pmsg;
Console.WriteLine(pmsg.Message);
}
Console.ReadKey();
}
}
}
public class PrivateMessage
{
public int PrivateMessageId { get; set; }
public string Message { get; set; }
public DateTime TimeStamp { get; set; }
// public int User1Id { get; set; }
public virtual string user1 { get; set; }
// public virtual User user1 { get; set; }
public virtual string user2 { get; set; }
//public int User1Id { get; set; }
// public virtual User user2 { get; set; }
}
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
}
public class PrivateMessageContext : DbContext
{
public DbSet<PrivateMessage> PrivateMessages { get; set; }
}
public class Send
{
/* void Send(userTo, userFrom, message)
{
using (var db = new PrivateMessageContext()) {
var query = from pm in db.PrivateMessages;
foreach(var msg in pm)
{
var user1 = msg.user1;
var user2 = msg.user2;
if ( (user1==userTo && user2==userFrom) || (user1==userFrom && user2==userTo))
{
msg.Message += message;
return;
}
else {
// pair doesn't exist
var PrivateMessage = new PrivateMessage { user1 = userFrom; user2 = userTo; TimeStamp = DateTime.Now; Message = message; };
db.PrivateMessages.Add(PrivateMessage);
db.SaveChanges();
}
}
}*/
}
}
I am now stuck on two things - how to make a callable class which checks if there is previous message history (the Send() ) and how to use the User username instead of strings...
Thank you
*update 3*
static void Main(string[] args)
{
using(var db = new PrivateMessageContext())
{
Console.Write("Enter message: ");
var message = Console.ReadLine();
var userFrom = "userFrom";
var userTo = "userTo";
var messageDetail = new PrivateMessageDetail(MessageDate = DateTime.Now, FromUser = userFrom, message = message);
var pm = new PrivateMessageHeader { User1 = userFrom, User2 = userTo, TimeStamp = DateTime.Now, Message = messageDetail };
db.PrivateMessages.Add(pm);
db.SaveChanges();
// Display all Blogs from the database
foreach(var pmsg in db.PrivateMessages)
{
var query = pmsg;
Console.WriteLine(pmsg.Message);
}
Console.ReadKey();
}
}
}
public class PrivateMessageContext : DbContext
{
public DbSet<PrivateMessageHeader> PrivateMessages { get; set; }
}
What you probably want is some kind of master/detail. What you would do is create a PrivateMessageHeader type, and this would contain the participants in the private message. Then you would have a PrivateMessageDetail type that would contain the actual messages. There would be a 1 to many association between Header and details.
So something like this:
public class PrivateMessageHeader {
public PrivateMessageHeader() { Messages = new List<PrivateMessageDetail>; }
public int PrivateMessageHeaderId {get;set;}
public DateTime ThreadTime {get;set;} // Date of the start of thread
public string User1 {get;set;}
public string User2 {get;set;} // this could be made to a list to allow multiples
public ICollection<PrivateMessageDetail> Messages {get;set;}
}
public class PrivateMessageDetail {
public int PrivateMessageDetailId {get;set;}
public DateTime MessageDate {get;set;}
public string FromUser {get;set;} // Don't need ToUser, it's already in header
public string Message {get;set;}
public PrivateMessageHeader parent {get;set;}
}

c# List adding items using For loop

I have a problem with c# List, not sure where I'm missing the point while adding a new object to the Managepagesid List!
public class Clients
{
[BsonId]
public string Id { get; set; } //Object ID managed by MongoDb
public string Name { get; set; } //Client Name
public string Phone { get; set; } //Client Phone
public string Email { get; set; } //Client E-mail
public string Username { get; set; } //Client Username
public DateTime LastModified { get; set; } //Client Last Login
public string FB_User_Id { get; set; } //Client FB User ID
public AccessToken UserAccessToken { get; set; } //AccessToken which is stored while user is logged in.
public List<ManagePages> Manage_Pages_id { get; set; } //The pages maintained by the specific client
}
I'm trying to access add a new item into ManagePage_id list but its thrwing some null exception.. Help!
Clients client = new Clients();
client.FB_User_Id = FBData.id;
client.Name = FBData.name;
client.Email = FBData.email;
client.Username = FBData.username;
for (int index = 0; index < FBData.accounts["data"].Count; index++)
{
ManagePages pagedetails = new ManagePages()
{
page_id = FBData.accounts["data"][index].id,
page_name = FBData.accounts["data"][index].name,
ManagePages_AccessTokens = new AccessToken()
{
AccessToken_accessToken = FBData.accounts["data"][index].access_token,
AccessToken_expiryDate = DateTime.Now
},
ManagePages_category = FBData.accounts["data"][index].category
};
var category = pagedetails.ManagePages_category;
client.Manage_Pages_id.Add(pagedetails);
}
Stack Trace added!
Exception>System.NullReferenceExceptionObject reference not set to an instance of an object. at Vega_MongoDB.FBDataAccess.ClientFBRepository.ClientsData(String accessToken) in g:\Development\Vega_MongoDB\Vega_MongoDB\FBDataAccess\ClientFBRepository.cs:line 48
at Vega_MongoDB.Models.ClientRepository..ctor(String connection) in g:\Development\Vega_MongoDB\Vega_MongoDB\Models\Clients\ClientRepository.cs:line 47
at Vega_MongoDB.Models.ClientRepository..ctor() in g:\Development\Vega_MongoDB\Vega_MongoDB\Models\Clients\ClientRepository.cs:line 23
at Vega_MongoDB.Controllers.ClientsController..cctor() in g:\Development\Vega_MongoDB\Vega_MongoDB\Controllers\ClientsController.cs:line 18
And I have checked the pagedetails object, it contains all the data..
Thanks
Vishnu
You should create an instance of the list before adding item:
client.Manage_Pages_id = new List<ManagePages>();
for (int index = 0; index < FBData.accounts["data"].Count; index++)
{
ManagePages pagedetails = new ManagePages()
{
page_id = FBData.accounts["data"][index].id,
page_name = FBData.accounts["data"][index].name,
ManagePages_AccessTokens = new AccessToken()
{
AccessToken_accessToken = FBData.accounts["data"][index].access_token,
AccessToken_expiryDate = DateTime.Now
},
ManagePages_category = FBData.accounts["data"][index].category
};
var category = pagedetails.ManagePages_category;
client.Manage_Pages_id.Add(pagedetails);
}
Try adding this to your class:
public Clients()
{
this.Manage_Pages_id = new List<ManagePages>();
}
You need to initialize your list before adding anything to it. You can do this in the constructor for the Clients class or in your calling code (as Artem suggested).
public class Clients
{
//properties
public Clients()
{
Manage_Pages_id = new List<ManagePages>();
}
}

Categories