Add to .NET Core Configuration Later in the Program - configuration

This is my setup for my .NET Core application:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.ConfigureAppConfiguration((builderContext, config) =>
{
var entryAssemblyFolder = new FileInfo(Assembly.GetEntryAssembly().Location).DirectoryName;
IHostingEnvironment env = builderContext.HostingEnvironment;
config
.SetBasePath(entryAssemblyFolder)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
.UseStartup<Startup>()
.Build();
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public static IConfiguration Configuration { get; set; }
}
All of the above runs when the program starts. At a later point in time during the execution of the program, I'd like to be able to add additional configuration based on data which isn't known at startup time. The following is pseudo-code since there's no parameter to the ConfigurationBuilder constructor:
public class Helper
{
public void Add(string key, string value)
{
//pseudo-code:
var builder = new ConfigurationBuilder(Startup.Configuration);
builder.AddInMemoryCollection(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(key, value)
});
Startup.Configuration = builder.Build();
}
}
How can I add to the existing configuration, while keeping what's already there (including the reloadOnChange: true) ?
Thanks,

I did some more digging into the source of MemoryConfigurationProvider. The provider copies the initial data into it's own Data property.
So even if you update your original dictionary, the provider will still only have the original values, when it was initialized.
Haven't tried the GetReloadToken approach which might work, but here is an alternative more direct approach.
Consider this be your configuration:
public class MyConfiguration
{
public static Dictionary<string,string> InMemoryCollection =
new Dictionary<string, string>
{
{"InMemoryCollection:Option1", "value1"},
{"InMemoryCollection:Option2", "value2"}
};
}
Initialize your configuration:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(builder => {
builder.AddInMemoryCollection(MyConfiguration.InMemoryCollection);
})
.UseStartup<Startup>();
Assume you have a controller to read and enter new values:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private IConfigurationRoot configuration;
/// <summary>
/// Initializes a new instance of the <see cref="ValuesController"/> class.
/// Value injected through DI.
/// </summary>
/// <param name="configuration">The configuration.</param>
public ValuesController(IConfiguration configuration)
{
this.configuration = (IConfigurationRoot)configuration;
}
[HttpGet]
/// <summary>
/// Get in memory values.
/// </summary>
/// <returns></returns>
public IDictionary<string,string> Get()
{
var result = new Dictionary<string, string>();
this.configuration.GetSection("InMemoryCollection").Bind(result);
return result;
}
[HttpPost]
/// <summary>
/// Enter a new value.
/// </summary>
/// <param name="value">The value.</param>
public void Post([FromBody] string value)
{
//get the provider instance from the configuration root.
MemoryConfigurationProvider memoryProvider =
(MemoryConfigurationProvider)this.configuration.Providers
.First(p =>
p.GetType() == typeof(MemoryConfigurationProvider));
//add the new option into the providers data collection.
var nextKey = MyConfiguration.InMemoryCollection.Count + 1;
memoryProvider.Add(
$"InMemoryCollection:Option{nextKey}",
value);
}
}
Note: I don't know the details/requirements for configuration options that are added dynamically, but using the AddInMemoryCollection approach doesn't look like it's the best of choices. Unless of course, you deliberately don't want to persist them.
I am fairly certain though that there are other approaches/solutions to resolve the issue, without necessarily using the Microsoft.Extensions.Configuration APIs.

Related

Dictionary<string,long> is not being serialized

I am trying to serialize a simple class:
public class Offer_RPC
{
/// <summary>
/// this dictionary contains your requested additions ans substractions, in mojos.<br/>
/// if you wan to offer an nft for example, use the launcher id such as <br/>
/// "1": 1000000000000 (offer for 1 xch) <br/>
/// "cc4138f8debe4fbedf26ccae0f965be19c67a49d525f1416c0749c3c865dxxx", -1 <br/>
/// </summary>
public Dictionary<string, long> offer = new Dictionary<string, long>();
public override string ToString()
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.WriteIndented = false;
options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
string jsonString = JsonSerializer.Serialize(this, options: options);
return jsonString;
}
}
when calling .ToString(), the resulting json is {}
This is my test method:
[Fact]
public void TestOffer()
{
Offer_RPC test = new Offer_RPC();
test.offer.Add("1", 1);
test.offer.Add("2", -1);
string json = test.ToString();
}
offer is a field and by default fields are not serialized by the System.Text.Json serialiser.
You can:
Make offer a property:public Dictionary<string, long> Offer { get; } = new ...
Include fields:
var options = new JsonSerializerOptions
{
IncludeFields = true,
};
var json = JsonSerializer.Serialize(o, options);
just fix the class offer property by adding a getter
public Dictionary<string, long> offer { get; } = new Dictionary<string, long>();
The Issue is that offer is not a public Property. The class should look like this:
public class Offer_RPC
{
public Offer_RPC()
{
offer = new Dictionary<string, long>();
}
/// <summary>
/// this dictionary contains your requested additions ans substractions, in mojos.<br/>
/// if you wan to offer an nft for example, use the launcher id such as <br/>
/// "1": 1000000000000 (offer for 1 xch) <br/>
/// "cc4138f8debe4fbedf26ccae0f965be19c67a49d525f1416c0749c3c865dxxx", -1 <br/>
/// </summary>
public Dictionary<string, long> offer { get; set; }
public override string ToString()
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.WriteIndented = false;
options.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
string jsonString = JsonSerializer.Serialize(this, options: options);
return jsonString;
}
}

Built in JSON parser in .NET framework [duplicate]

I'm trying to build a Metro application for Windows 8 on Visual Studio 2011.
and while I'm trying to do that, I'm having some issues on how to parse JSON without JSON.NET library (It doesn't support the metro applications yet).
Anyway, I want to parse this:
{
"name":"Prince Charming",
"artist":"Metallica",
"genre":"Rock and Metal",
"album":"Reload",
"album_image":"http:\/\/up203.siz.co.il\/up2\/u2zzzw4mjayz.png",
"link":"http:\/\/f2h.co.il\/7779182246886"
}
You can use the classes found in the System.Json Namespace which were added in .NET 4.5. You need to add a reference to the System.Runtime.Serialization assembly
The JsonValue.Parse() Method parses JSON text and returns a JsonValue:
JsonValue value = JsonValue.Parse(#"{ ""name"":""Prince Charming"", ...");
If you pass a string with a JSON object, you should be able to cast the value to a JsonObject:
using System.Json;
JsonObject result = value as JsonObject;
Console.WriteLine("Name .... {0}", (string)result["name"]);
Console.WriteLine("Artist .. {0}", (string)result["artist"]);
Console.WriteLine("Genre ... {0}", (string)result["genre"]);
Console.WriteLine("Album ... {0}", (string)result["album"]);
The classes are quite similar to those found in the System.Xml.Linq Namespace.
I use this...but have never done any metro app development, so I don't know of any restrictions on libraries available to you. (note, you'll need to mark your classes as with DataContract and DataMember attributes)
public static class JSONSerializer<TType> where TType : class
{
/// <summary>
/// Serializes an object to JSON
/// </summary>
public static string Serialize(TType instance)
{
var serializer = new DataContractJsonSerializer(typeof(TType));
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, instance);
return Encoding.Default.GetString(stream.ToArray());
}
}
/// <summary>
/// DeSerializes an object from JSON
/// </summary>
public static TType DeSerialize(string json)
{
using (var stream = new MemoryStream(Encoding.Default.GetBytes(json)))
{
var serializer = new DataContractJsonSerializer(typeof(TType));
return serializer.ReadObject(stream) as TType;
}
}
}
So, if you had a class like this...
[DataContract]
public class MusicInfo
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string Artist { get; set; }
[DataMember]
public string Genre { get; set; }
[DataMember]
public string Album { get; set; }
[DataMember]
public string AlbumImage { get; set; }
[DataMember]
public string Link { get; set; }
}
Then you would use it like this...
var musicInfo = new MusicInfo
{
Name = "Prince Charming",
Artist = "Metallica",
Genre = "Rock and Metal",
Album = "Reload",
AlbumImage = "http://up203.siz.co.il/up2/u2zzzw4mjayz.png",
Link = "http://f2h.co.il/7779182246886"
};
// This will produce a JSON String
var serialized = JSONSerializer<MusicInfo>.Serialize(musicInfo);
// This will produce a copy of the instance you created earlier
var deserialized = JSONSerializer<MusicInfo>.DeSerialize(serialized);
For those who do not have 4.5, Here is my library function that reads json. It requires a project reference to System.Web.Extensions.
using System.Web.Script.Serialization;
public object DeserializeJson<T>(string Json)
{
JavaScriptSerializer JavaScriptSerializer = new JavaScriptSerializer();
return JavaScriptSerializer.Deserialize<T>(Json);
}
Usually, json is written out based on a contract. That contract can and usually will be codified in a class (T). Sometimes you can take a word from the json and search the object browser to find that type.
Example usage:
Given the json
{"logEntries":[],"value":"My Code","text":"My Text","enabled":true,"checkedIndices":[],"checkedItemsTextOverflows":false}
You could parse it into a RadComboBoxClientState object like this:
string ClientStateJson = Page.Request.Form("ReportGrid1_cboReportType_ClientState");
RadComboBoxClientState RadComboBoxClientState = DeserializeJson<RadComboBoxClientState>(ClientStateJson);
return RadComboBoxClientState.Value;
I needed a JSON serializer and deserializer without any 3rd party dependency or nuget, that can support old systems, so you don't have to choose between Newtonsoft.Json, System.Text.Json, DataContractSerializer, JavaScriptSerializer, etc. depending on the target platform.
So I have started this open source (MIT) project here:
https://github.com/smourier/ZeroDepJson
It's just one C# file ZeroDepJson.cs, compatible with .NET Framework 4.x to .NET Core, and .NET 5.
Note it's probably not as good as all the aforementioned libraries (especially in performance area), but it should be reasonably ok and friction-free.
Have you tried using JavaScriptSerializer ?
There's also DataContractJsonSerializer
I have written a small construct to do that long back, it requires System.Runtime.Serialization.Json namespace. It uses DataContractJsonSerializer to serialize & deserialize object with a static method JConvert.
It works with small set of data but haven't tested with big data source.
JsonHelper.cs
// Json Serializer without NewtonSoft
public static class JsonHelper
{
public static R JConvert<P,R>(this P t)
{
if(typeof(P) == typeof(string))
{
var return1 = (R)(JsonDeserializer<R>(t as string));
return return1;
}
else
{
var return2 = (JsonSerializer<P>(t));
R result = (R)Convert.ChangeType(return2, typeof(R));
return result;
}
}
private static String JsonSerializer<T>(T t)
{
var stream1 = new MemoryStream();
var ser = new DataContractJsonSerializer(typeof(T));
ser.WriteObject(stream1, t);
stream1.Position = 0;
var sr = new StreamReader(stream1);
return (sr.ReadToEnd());
}
private static T JsonDeserializer<T>(string jsonString)
{
T deserializedUser = default(T);
var ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
var ser = new DataContractJsonSerializer(typeof(T));
deserializedUser = (T)ser.ReadObject(ms);// as T;
ms.Close();
return deserializedUser;
}
}
Syntax:
To use the JsonHelper you need to call
JConvert<string,object>(str); //to Parse string to non anonymous <object>
JConvert<object,string>(obj); //to convert <obj> to string
Example:
Suppose we have a class person
public class person
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
var obj = new person();//"vinod","srivastav");
obj.FirstName = "vinod";
obj.LastName = "srivastav";
To convert the person object we can call:
var asText = JsonHelper.JConvert<person,string>(obj); //to convert <obj> to string
var asObject = JsonHelper.JConvert<string,person>(asText); //to convert string to non-anonymous object
You can use DataContractJsonSerializer. See this link for more details.
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
namespace OTL
{
/// <summary>
/// Before usage: Define your class, sample:
/// [DataContract]
///public class MusicInfo
///{
/// [DataMember(Name="music_name")]
/// public string Name { get; set; }
/// [DataMember]
/// public string Artist{get; set;}
///}
/// </summary>
/// <typeparam name="T"></typeparam>
public class OTLJSON<T> where T : class
{
/// <summary>
/// Serializes an object to JSON
/// Usage: string serialized = OTLJSON<MusicInfo>.Serialize(musicInfo);
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
public static string Serialize(T instance)
{
var serializer = new DataContractJsonSerializer(typeof(T));
using (var stream = new MemoryStream())
{
serializer.WriteObject(stream, instance);
return Encoding.Default.GetString(stream.ToArray());
}
}
/// <summary>
/// DeSerializes an object from JSON
/// Usage: MusicInfo deserialized = OTLJSON<MusicInfo>.Deserialize(json);
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
public static T Deserialize(string json)
{
if (string.IsNullOrEmpty(json))
throw new Exception("Json can't empty");
else
try
{
using (var stream = new MemoryStream(Encoding.Default.GetBytes(json)))
{
var serializer = new DataContractJsonSerializer(typeof(T));
return serializer.ReadObject(stream) as T;
}
}
catch (Exception e)
{
throw new Exception("Json can't convert to Object because it isn't correct format.");
}
}
}
}

#ComponentScan JSON information replaces the HTML views

Maybe the title is not the best to explain the problem, but I'll try to explain it as best as possible.
I have an Spring Boot Application using JPA and MySQL, so in order to check everything worked properly, I made a simple CRUD test for my database, and I found problems with autowiring which are explained in my previous question. The solution for those problems was just adding the #ComponentScan annotation to my Application.java.
It was the solution for the test because it run without problems, but then I find another problem. Apart from the test, I need my application to show a list of Proposals made by some Users and also some Comments. Before adding that annotation, the HTMLs showed the correct information, but after adding it shows information about the database in JSON format on the main page and if I try to navigate to "localhost:8080/viewProposal" p.e. it shows a WhiteLabel error page with error code 404. I have no idea why it is replacing the HTMLs because I have just one controller and is not a RESTController. These are my classes:
Application.java
#SpringBootApplication
#EntityScan("persistence.model")
#EnableJpaRepositories("persistence.repositories")
#ComponentScan("services.impl")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
MainController.java
#Controller
#RequestMapping("/")
public class MainController {
private static final Logger logger = Logger.getLogger(MainController.class);
private List<SseEmitter> sseEmitters = Collections.synchronizedList(new ArrayList<>());
private Map<String, Proposal> proposals = generateProposals();
#RequestMapping({"/live","/"})
public String landing(Model model) {
return "index";
}
#RequestMapping("/viewProposal")
public String viewProposal(Model model, Long id) {
//put the object in the map
return "viewProposal";
}
#KafkaListener(topics = "newVote")
public void listen(String data) {
String[] contents = data.split(";");
if(contents.length!=2)
return;
Proposal p;
int newVote;
if (proposals.containsKey(contents[0]))
p = proposals.get(contents[0]);
else {
p = new Proposal();
p.setTitle(contents[0]);
proposals.put(p.getTitle(), p);
}
if (contents[1].equals("+"))
newVote = +1;
else if (contents[1].equals("-"))
newVote = -1;
else
newVote = 0;
p.setNumberOfVotes(p.getNumberOfVotes() + newVote);
logger.info("New message received: \"" + data + "\"");
}
private static Map<String, Proposal> generateProposals() {
Map<String, Proposal> lista = new HashMap<String, Proposal>();
Proposal p = new Proposal();
p.setTitle("tituloPrueba");
lista.put("tituloPrueba", p);
return lista;
}
#ModelAttribute("proposals")
public Map<String, Proposal> getProposals() {
return proposals;
}
public void setProposals(Map<String, Proposal> proposals) {
this.proposals = proposals;
}
}
MvcConfiguration
#Configuration
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/resources/templates/");
resolver.setSuffix(".html");
return resolver;
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.TEXT_HTML);
}
}
If you want to see the rest of the classes, please go to my previous question everything is in there.
Thanks in advance.

Dependency Issues In AutoMapper Type Convertors

I've encountered a problem using AutoMapper and Windsor. I've created a custom type convertor that depends on another object, which can be resolved from the container but when I attempt to use the convertor in a mapping process, an AutoMapper.AutoMapperMappingException is thrown, stating that my Type Convertor does not have a default constructor.
I've recreated the issue in the proof of concept code below:
using System;
using System.Reflection;
using AutoMapper;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using NUnit.Framework;
public class ObjectOne
{
public string Name { get; set; }
public string Description { get; set; }
}
public class ObjectTwo
{
public string ObjName { get; set; }
public string ObjDescr { get; set; }
}
public interface ILoggingService
{
void Log(string message);
}
public class ConsoleLogger : ILoggingService
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class MyObjectConvertor : ITypeConverter<ObjectOne, ObjectTwo>
{
private readonly ILoggingService _loggingService;
public MyObjectConvertor(ILoggingService loggingService)
{
if (loggingService == null) throw new ArgumentNullException("loggingService");
_loggingService = loggingService;
}
public ObjectTwo Convert(ResolutionContext context)
{
_loggingService.Log(MethodBase.GetCurrentMethod().ToString());
var source = (ObjectOne)context.SourceValue;
return new ObjectTwo { ObjName = source.Name, ObjDescr = source.Description };
}
public void LogIt(string message)
{
_loggingService.Log(message);
}
}
public class MappingContractsWindsorInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<ILoggingService>().ImplementedBy<ConsoleLogger>(),
Component.For<MyObjectConvertor>());
}
}
[TestFixture]
public class MappingTester
{
private IWindsorContainer container;
[TestFixtureSetUp]
public void SetupFixture()
{
container = new WindsorContainer();
container.Install(new MappingContractsWindsorInstaller());
Mapper.CreateMap<ObjectOne, ObjectTwo>().ConvertUsing<MyObjectConvertor>();
Mapper.Configuration.ConstructServicesUsing(container.Resolve);
Mapper.AssertConfigurationIsValid();
}
[Test]
public void MyObjectConvertorReturnedWithLoggerInjectedOk()
{ // Proof that the Convertor is returned from the
// container with the dependency fulfilled
var conv = container.Resolve<MyObjectConvertor>();
conv.LogIt("Hello World");
}
[Test]
public void ObjectOneMapsToTwo()
{
var source = new ObjectOne()
{
Name = "Foo",
Description = "Bar"
};
var result = Mapper.Map<ObjectOne, ObjectTwo>(source);
Assert.That(result.ObjName == source.Name);
Assert.That(result.ObjDescr == source.Description);
}
}
When the tests are ran, the following exception is thrown during ObjectOneMapsToTwo():
Test 'MappingTester.ObjectOneMapsToTwo' failed: AutoMapper.AutoMapperMappingException : Trying to map ObjectOne to ObjectTwo.
Using mapping configuration for ObjectOne to ObjectTwo
Exception of type 'AutoMapper.AutoMapperMappingException' was thrown.
----> System.ArgumentException : Type 'MyObjectConvertor' does not have a default constructor
at AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context)
at AutoMapper.MappingEngine.Map(Object source, Type sourceType, Type destinationType, Action`1 opts)
at AutoMapper.MappingEngine.Map[TSource,TDestination](TSource source)
at AutoMapper.Mapper.Map[TSource,TDestination](TSource source)
Class1.cs(99,0): at MappingTester.ObjectOneMapsToTwo()
--ArgumentException
at System.Linq.Expressions.Expression.New(Type type)
at AutoMapper.DelegateFactory.<>c__DisplayClass1.<CreateCtor>b__0(Type t)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at AutoMapper.DelegateFactory.CreateCtor(Type type)
at AutoMapper.Mappers.ObjectCreator.CreateObject(Type type)
at AutoMapper.MappingExpression`2.<ConvertUsing>b__1a[TTypeConverter]()
at AutoMapper.DeferredInstantiatedConverter`2.Convert(ResolutionContext context)
at AutoMapper.MappingExpression`2.<>c__DisplayClass15.<ConvertUsing>b__14(ResolutionContext context)
at AutoMapper.Mappers.TypeMapObjectMapperRegistry.CustomMapperStrategy.Map(ResolutionContext context, IMappingEngineRunner mapper)
at AutoMapper.Mappers.TypeMapMapper.Map(ResolutionContext context, IMappingEngineRunner mapper)
at AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context)
This project is referencing the following:
<packages>
<package id="NUnit" version="2.5.10.11092" />
<package id="Castle.Core" version="2.5.2" />
<package id="Castle.Windsor" version="2.5.3" />
<package id="AutoMapper" version="2.0.0" />
</packages>
The test MyObjectConvertorReturnedWithLoggerInjectedOk() passes, proving that the typeconvertor is being returned from the container with the dependency passed in OK. But when Automapper tries to use the convertor, it throws the exception.
Any help on this would be appreciated.
Thanks in advance.
This is actually a bug in AutoMapper, fixed after the 2.0 release and will be out in the upcoming 2.1 release. You can pull down the nightlies to get the latest stable drop from AutoMapper.org

Customizing Linq to Sql DataContext

Is there anyway simple ways to add a property to Linq to Sql generated entity to reference its DataContext?
For example:
var myContext = new DataContext();
var product = context.Products.Where(p => p.Id == productId).SingleOrDefault();
and product entity has a property called "Context" (product.Context) that has a reference to the myContext, datacontext.
I know how to customize generated entities. My question is how to customize (i think DataContext) to set 'Context' property of each instance that it creates to itself.
I'm not sure I'm doing the right thing or not. I want to write a business encapsulation model that has best performance with less code.
As i googled around I have find out that the DataContext object is very lightweight and there for I thought it would be a good way to add an instance of DataContext to each object. This will reduce the need to attach, detached objects again to a new instance of datacontext every time I want to update or delete them.
If you have any other solution I will really appreciate it.
Thanks
Is there a simple way to add a property to Linq to Sql generated entity to reference its DataContext?
There is no simple way to achieve this.
In doing this, you defeat part of the purpose of LINQ-to-SQL. One of the purposes is to allow to you work with the objects that you have, and not have to change your design based on database considerations.
In attaching the DataContext to the database, you are coupling your representation in code with the means to persist it, which is generally a bad design idea.
If you feel you must do this though, you can always derive from the class and then implement an interface on that class which exposes the DataContext.
I recommend implementing the interface, since you can't indicate a base class to the LINQ-to-SQL designer, and you want to be able for all of your entities to have a shared implementation.
See here: Determine the source DataContext for a Linq to Sql query
I asked more or less the same question. You can get the context from the IQueryable that a linq to sql query returns, but not from the entity itself as far as I know.
Actually, I agree to casperOne. If you really have to need this, I remembered that the classes that linq-to-sql generates are partial. So you can write a partial class to any class you want and add extended functionalities to it.
Pass the data context as a ref parameter to a custom method on your partial Product object:
public partial class Product
{
public string GetSomethingElse(ref DataContext dbase)
{
return dbase.OtherTableWhatever.Count.ToString(); // whatever
}
}
Inside your aspx.cs:
var context = new DataContext();
var product = context.Products.SingleOrDefault(x => x.id == 1);
var s = product.GetSomethingElse(ref context);
Here is a custom wrapper I made for System.Data.Linq. It contains a find method, so instead of your code:
var myContext = new DataContext();
var product = context.Products.Where(p => p.Id == productId).SingleOrDefault();
you can do this
var myContext = new DataContext();
var product = context.Products.Find(productId); //(assuming productId is your primary key)
You can grab the code below and do any custom modifications you wish to set product.Context, but this is an example of modifying the DataContext.
I also made save and delete methods. You'll notice I go out a regrab the record even though it is being passed in. I do this because the record might get detached from the context and not update. If anyone would like the full code I can post the github link.
public abstract class DbContext : IDisposable
{
#region Properties
private string _connectionString { get; set; }
private DataContext _context { get; set; }
#endregion
#region Constructor
public DbContext(string connectionString)
{
_connectionString = connectionString;
_context = new DataContext(_connectionString);
Initialized(_context);
}
public DbContext(string server, string database, string userID, string password)
{
_connectionString = string.Format(
"Server={0};Database={1};User Id={2};Password={3};MultipleActiveResultSets=true",
server,
database,
userID,
password);
_context = new DataContext(_connectionString);
Initialized(_context);
}
#endregion
#region Methods
/// <summary>
/// Is used to get the contents of a Sql Server Table.
/// </summary>
/// <typeparam name="TEntity">Type</typeparam>
/// <returns>Table</returns>
public Table<TEntity> GetTable<TEntity, TPKType>()
where TEntity : DbTableEquatable<IDbTableEquatable<TPKType>>
where TPKType : struct
{
return _context.GetTable<TEntity>();
}
/// <summary>
/// Is used to get the contents of a Sql Server Table.
/// </summary>
/// <typeparam name="TEntity">Type</typeparam>
/// <returns>Table</returns>
public Table<TEntity> GetTable<TEntity>()
where TEntity : DbTableEquatable<IDbTableEquatable<long>>
{
return GetTable<TEntity, long>();
}
protected virtual void Initialized(DataContext context) { }
/// <summary>
/// Saves the changes to the database. In order to save the table must inherit from DbTableEquatable
/// and be a type of IDbTableEquatable and the Primary Key Type must be a C# Structure
/// </summary>
/// <typeparam name="TEntity">Record Type</typeparam>
/// <typeparam name="TPKType">Primary Key Type</typeparam>
/// <param name="entity">Record</param>
public virtual void SaveChanges<TEntity, TPKType>(TEntity entity)
where TEntity : DbTableEquatable<IDbTableEquatable<TPKType>>
where TPKType : struct
{
var changedList = _context.GetTable<TEntity>();
var ID = entity.GetType().GetProperty("ID").GetValue(entity);
_preprocessSave<TEntity, TPKType>(entity);
// Save changes
if (Convert.ToInt64(ID) == 0)
{
// Insert
// If No ID we need to insert on submit
_context.GetTable<TEntity>().InsertOnSubmit((TEntity)entity);
_context.SubmitChanges();
}
else
{
// Update
var item = changedList.Where(w => w.Equals(entity)).FirstOrDefault();
ReflectionManager.SetValuesWithSkip(entity, item, "ID");
_context.SubmitChanges();
}
Refresh();
}
/// <summary>
/// Saves the changes to the database. In order to save the Table the Record is from must inherit from DbTableEquatable
/// and be a type of IDbTableEquatable and the Primary Key Type must be a C# Structure
/// </summary>
/// <typeparam name="TEntity">Record Type</typeparam>
/// <param name="entity">Record</param>
public virtual void SaveChanges<TEntity>(TEntity entity)
where TEntity : DbTableEquatable<IDbTableEquatable<long>>
{
SaveChanges<TEntity, long>(entity);
}
/// <summary>
/// Saves any non committed changes to the database
/// </summary>
public void SaveChanges()
{
_context.SubmitChanges();
Refresh();
}
/// <summary>
/// Marks the record as delete and will be deleted when saved
/// </summary>
/// <typeparam name="TEntity">Record Type</typeparam>
/// <typeparam name="TPKType">Primary Key Type</typeparam>
/// <param name="entity">Record</param>
public virtual void DeleteOnSave<TEntity, TPKType>(TEntity entity)
where TEntity : DbTableEquatable<IDbTableEquatable<TPKType>>
where TPKType : struct
{
var item = _context.GetTable<TEntity>().Where(w => w.Equals(entity)).FirstOrDefault();
_context.GetTable<TEntity>().DeleteOnSubmit((TEntity)item);
}
/// <summary>
/// Marks the record as delete and will be deleted when saved
/// </summary>
/// <typeparam name="TEntity">Record Type</typeparam>
/// <param name="entity">Record</param>
public virtual void DeleteOnSave<TEntity>(TEntity entity)
where TEntity : DbTableEquatable<IDbTableEquatable<long>>
{
DeleteOnSave<TEntity, long>(entity);
}
protected virtual void _preprocessSave<TEntity, TPKType>(TEntity entity)
where TEntity : DbTableEquatable<IDbTableEquatable<TPKType>>
where TPKType : struct
{
}
protected virtual void _preprocessSave<TEntity>(TEntity entity)
where TEntity : DbTableEquatable<IDbTableEquatable<long>>
{
_preprocessSave<TEntity, long>(entity);
}
public void Dispose()
{
_connectionString = "";
_context.Dispose();
_context = null;
}
public virtual void Refresh<TEntity>(RefreshMode mode, TEntity entity) where TEntity : class
{
_context.Refresh(RefreshMode.OverwriteCurrentValues, entity);
}
public virtual void Refresh()
{
_context = new DataContext(_connectionString);
}
public virtual void Refresh<TEntity>(RefreshMode mode, params TEntity[] entities) where TEntity : class
{
_context.Refresh(RefreshMode.OverwriteCurrentValues, entities);
}
public virtual void Refresh<TEntity>(RefreshMode mode, IEnumerable<TEntity> entities) where TEntity : class
{
_context.Refresh(RefreshMode.OverwriteCurrentValues, entities);
}
#endregion
}
Extenstions
public static class Extension
{
public static TEntity Find<TEntity, TPKType>(this Table<TEntity> table, TPKType ID, string pkName = "ID")
where TEntity : DbTableEquatable<IDbTableEquatable<TPKType>>
where TPKType : struct
{
TEntity copy = Activator.CreateInstance<TEntity>();
// set value through reflection
copy.GetType().GetProperty(pkName).SetValue(copy, ID, null);
return (TEntity)table.Where(w => w.Equals(copy)).FirstOrDefault();
}
public static TEntity Find<TEntity>(this Table<TEntity> table, long ID, string pkName = "ID")
where TEntity : DbTableEquatable<IDbTableEquatable<long>>
{
TEntity copy = Activator.CreateInstance<TEntity>();
// set value through reflection
copy.GetType().GetProperty(pkName).SetValue(copy, ID, null);
return (TEntity)table.Where(w => w.Equals(copy)).FirstOrDefault();
}
}
}
Interface/Abstraction
/// <summary>
/// This Class Assumes the type T has a property called ID. MUST be
/// used with IDbEquatable
/// </summary>
/// <typeparam name="T">Class Type</typeparam>
public abstract class DbTableEquatable<T> : IEquatable<T> where T : class
{
public bool Equals(T other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null))
{
return false;
}
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other))
{
return true;
}
return ((dynamic)other).ID == ((dynamic)this).ID;
}
}
/// <summary>
/// Needs to be inherited from in order for Ion.Data.Linq functions to work
/// </summary>
/// <typeparam name="T">Primary Key Type</typeparam>
public interface IDbTableEquatable<T>
{
T ID { get; set; }
}
here is a table implementation
[Table(Name = "Crews")]
public class Crew : DbTableEquatable<IDbTableEquatable<long>>, IDbTableEquatable<long>
{
[Column(IsPrimaryKey = true, DbType = "Bigint NOT NULL IDENTITY", AutoSync = AutoSync.OnInsert, IsDbGenerated = true)]
public long ID { get; set; }
[Column]
public string Alias { get; set; }
[Column]
public string DefaultForeground { get; set; }
[Column]
public string DefaultBackground { get; set; }
[Column]
public string AccountEmailAddress { get; set; }
[Column]
public long? CrewLeaderEmployeeID { get; set; }
[Column]
public string Comments { get; set; }
[Column]
public string Password { get; set; }
[Column]
public string PrivateICSLink { get; set; }
[Column]
public string PrivateXMLLink { get; set; }
}